Update gui controls without MethodInvoker (SynchronizationContext)

13 בנובמבר 2008

תגיות: ,
6 תגובות


שלום לכל הקוראים.


חבר שלי, שי קרן, הראה לי class חדש שלא הכרתי  בשם SynchronizationContext


אני רוצה שנכיר את אחד מהשימושים של ה Class הזה.


נחשוב על הסיטואציה הבאה: יש לנו מתודה בתוך Form שתפקידה לכתוב על Label את התאריך.


 




   1     private void UpdateLabel()


   2     {


   3         label1.Text = DateTime.Now.ToLongTimeString();


   4     }


ואנחנו מפעילים אותה מתוך Thread אחר

   

   1     private void WorkWithThread_Click(object sender, EventArgs e)


   2     {


   3         Thread thread = new Thread(delegate()


   4         {


   5             UpdateLabel();


   6         });


   7 


   8         thread.Start();


   9     }



מה שיקרה בפועל, שהאפליקצייה תתרסק עם ה Exception הבא:


 




Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on



הסבה לכך היא, שאנחנו מנסים לעדכן Control שנוצר ב Thread של ה GUI מתוך Thread אחר.


הפתרון הפשוט הוא, שבתוך ה UpdateLabel נבדוק באיזה Thread אחנו נמצאים.




   1     private void UpdateLabel()


   2     {


   3         if (InvokeRequired)


   4         {


   5             Invoke(new MethodInvoker(delegate


   6                 {


   7                     label1.Text = DateTime.Now.ToLongTimeString();


   8                 }));


   9         }


   10        else


   11        {


   12            label1.Text = DateTime.Now.ToLongTimeString();


   13        }


   14     }



 


בשורה 2 אחנו בודקים האם אנחנו כרגע בתוך Thread אחר מה Thread שיצר את ה Control, במידה וכן, נפעיל את הקוד של עידכון ה GUI ע"י שימוש ב MethodInvoker .


 


אפשר לפתור את הבעייה גם באמצעות ה SynchronizationContext


במקום שכל מתודה בצד של ה GUI תצטרך לבדוק לפני העידכון של ה GUI האם InvokeRequired


אפשר לשמור את ה SynchronizationContext  ולהפעיל את ה מתודה מתוך ה Thread על ידי שימוש ב SynchronizationContext 




   1     private SynchronizationContext m_SynchronizationContext = null;


   2     public Form1()


   2     {


   3         InitializeComponent();


   4         m_SynchronizationContext = SynchronizationContext.Current;


   5     }


   6 


   7     private void WorkWithThread_Click(object sender, EventArgs e)


   8     {


   10         Thread thread = new Thread(delegate()


   11         {


   12             m_SynchronizationContext.Post(delegate


   13             {


   14                 UpdateLabel();


   15             }, null);


   16         });


   17 


   18         thread.Start();


   19     }


   20 


   21 


   22     private void UpdateLabel()


   23     {


   24         label1.Text = DateTime.Now.ToLongTimeString();


   25     }



 


בהצלחה


 


 


 


 


 


 


 


 


 


 


 


 


 


 

הוסף תגובה
facebook linkedin twitter email

להגיב על Noam לבטל

האימייל לא יוצג באתר. שדות החובה מסומנים *

6 תגובות

  1. יצחק גוטויליג13 בנובמבר 2008 ב 8:12

    אפשר גם:
    private void WorkWithThread_Click(object sender, EventArgs e)
    {
    SynchronizationContext ctx = SynchronizationContext.Current;

    Thread thread = new Thread(delegate()
    {
    ctx.Post(delegate{UpdateLabel();},null);
    });
    }

    למעשה, זה המנוע שעומד מאחורי BackgroundWorker

    הגב
  2. Yoav14 בנובמבר 2008 ב 4:59

    Even outside the context of WinForms, this is very interesting.

    Thanks!

    הגב
  3. Noam14 בנובמבר 2008 ב 5:46

    Yes, In fact we can save a reference to threads.
    Thanks

    הגב
  4. אליעזר19 בפברואר 2012 ב 11:49

    הייתי שמח להבין מה עומד מאחורי שני הפתרונות – למה MethodInvoker פותר את הבעיה של Cross thread exception? וכן לגבי הפתרון השני, מה עומד מאחוריו?

    הגב
  5. אליעזר19 בפברואר 2012 ב 11:51

    הייתי שמח להבין מה עומד מאחורי ה exception – למה אי אפשר לגשת ל controls שנוצרו ב thread אחר? ואיך SynchronizationContext\MethodInvoker פותרים את הבעיה?

    הגב