Winform SynchronizationContext多執行緒更新畫面控制元件

懒树懒發表於2024-07-03

  SynchronizationContext在通訊中充當傳輸者的角色,實現功能就是一個執行緒和另外一個執行緒的通訊。

  需要注意的是,不是每個執行緒都附加SynchronizationContext這個物件,只有UI執行緒是一直擁有的。故獲取SynchronizationContext也只能在UI執行緒上進行SynchronizationContext context = SynchronizationContext.Current;

  那什麼時候會用到呢?

  在多執行緒操作時往往需要切回某個執行緒中去工作,等完成後再切回來。

  如主UI執行緒中建立了一個子執行緒A。A中新增了委託事件。UI執行緒中向A執行緒的類註冊了事件,當A執行緒觸發事件時去修改UI上的屬性如TEXT。

  這個時候往往要在UI執行緒向子執行緒註冊的事件方法中使用控制元件的invoke方法才能訪問UI執行緒中的控制元件,因為這些註冊的事件(委託)方法程式碼雖然看似寫在UI執行緒的Form類中,但實際上是註冊在了子執行緒A的事件中,它們是會被子執行緒A觸發事件時在子執行緒內部執行的。這樣,我們不得不在主UI執行緒的類的註冊事件方法中透過控制元件的Invoke方法才能訪問控制元件,這樣做十分麻煩。我們想和系統的控制元件事件一樣,直接在註冊的事件方法中訪問控制元件。那麼這個時候就可以用SynchronizationContext了。

  SynchronizationContext.Send(SendOrPostCallback d,object state);

  SynchronizationContext.Post(SendOrPostCallback d,object state);

  d 為一個沒有返回值,並且具有一個Object型別傳入引數的委託(SendOrPostCallback );

  state 為執行這個委託時的引數(object);

注意:

  SynchronizationContext的物件不是所有執行緒都被附加的,只有UI主執行緒會被附加。

  對於UI執行緒來說,是如何將SynchronizationContext這個物件附加到執行緒上的呢?

  在Form1 form = new Form1()之前,SynchronizationContext物件是為空,而當例項化Form1窗體後,SynchronizationContext物件就被附加到這個執行緒上了。

  所以可以得出答案了:當Control物件被建立的同時,SynchronizationContext物件也會被建立並附加到執行緒上。所以在使用時,一定要等窗體InitializeComponent(); 這個完成後 它才能得到一個不是NULL的物件.

  

  那麼SynchronizationContext的Send()和Post()二個方法有什麼區別呢?

  Send() 是簡單的在當前執行緒上去呼叫委託來實現(同步呼叫)。也就是在子執行緒上直接呼叫UI執行緒執行,等UI執行緒執行完成後子執行緒才繼續執行。

  Post() 是線上程池上去呼叫委託來實現(非同步呼叫)。這是子執行緒會從執行緒池中找一個執行緒去調UI執行緒,子執行緒不等待UI執行緒的完成而直接執行自己下面的程式碼。

  

例子:

複製程式碼
        /// <summary>
        /// 這裡需要在主執行緒裡定義,
        /// 並在主執行緒獲得context = SynchronizationContext.Current
        /// </summary>
        private SynchronizationContext context;
        /// <summary>
        /// 窗體載入
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            //此處就是之前提的在主執行緒獲得SynchronizationContext
            context = SynchronizationContext.Current;
            //之後可以開執行緒了
            Thread thread = new Thread(new ThreadStart(Start));
            thread.IsBackground = true;
            thread.Start();
        }
        /// <summary>
        /// 執行緒操作
        /// </summary>
        private void Start()
        { 
            for(int i=0;i<100;++i)
            {
                //這邊即可正常呼叫主介面的控制元件了
                context.Send(operation, i);//正確
                //按原先直接應用,因為使用到控制元件會報錯
                operation(i);//報錯
                Thread.Sleep(100);
            }
        }
        /// <summary>
        /// 執行緒操作
        /// </summary>
        /// <param name="obj"></param>
        private void operation(object obj)
        {
            textBox1.AppendText(obj.ToString() + "\r\n"); 
        }
複製程式碼

相關文章