.Net多線程總結(二)-BackgroundWorker

上篇文章介紹了多種線程的創建方式,以及winform在多線程編程時的特殊性,這篇我們來介紹一下異步編程的經典模式和微軟對其的實現


微軟推薦的異步操作模型是事件模型,也即用子線程通過事件來通知調用者自己的工作狀態,也就是設計模式中的observer模式,也可以看成是上文中線程類的擴展,最後實現後調用效果類似於

MyThread thread=new MyThread() thread.Work+=new ThreadWork(Calculate) thread.WorkComplete+=new WorkComplete(DisplayResult) Calculate(object sender, EventArgs e)){ .... } DisplayResult(object sender, EventArgs e)){ ... }

 

<例一>

 BackgroundWorker

上篇文章裏說到了控制權的問題,上面的模型在winform下使用有個問題就是執行上下文的問題,在回調函數中(比如<例一>中的DisplayResult中),我們不得不使用BeginInvoke,才能調用ui線程創建的控件的屬性和方法,

比如在上面net66的例子裏

//創建線程對象 _Task = new newasynchui(); //掛接進度條修改事件 _Task.TaskProgressChanged += new TaskEventHandler( OnTaskProgressChanged1 ); //在UI線程,負責更新進度條 private void OnTaskProgressChanged1( object sender,TaskEventArgs e ) { if (InvokeRequired ) //不在UI線程上,異步調用 { TaskEventHandler TPChanged1 = new TaskEventHandler( OnTaskProgressChanged1 ); this.BeginInvoke(TPChanged1,new object[] {sender,e}); Console.WriteLine("InvokeRequired=true"); } else { progressBar.Value = e.Progress; } }

 

<例二>

可以看到,在函數裏面用到了

if(InvokeRequired)

{...BeginInvoke....}

else

{....}

這個模式來保證方法在多線程和單線程下都可以運行,所以線程邏輯和界面邏輯混合在了一起,以至把以前很簡單的只需要一句話的任務:progressBar.Value = e.Progress;搞的很複雜,如果線程類作爲公共庫來提供,對編寫事件的人要求會相對較高,那麼有什麼更好的辦法呢?

其實在.Net2.0中微軟自己實現這個模式,製作了Backgroundworker這個類,他可以解決上面這些問題,我們先來看看他的使用方法

System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker(); //定義需要在子線程中乾的事情 bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork); //定義執行完畢後需要做的事情 bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); //開始執行 bw.RunWorkerAsync(); static void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) { MessageBox.Show("Complete"+Thread.CurrentThread.ManagedThreadId.ToString()); } static void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { MessageBox.Show(Thread.CurrentThread.ManagedThreadId); }

 

<例三>

注意我在兩個函數中輸出了當前線程的ID,當我們在WindowsForm程序中執行上述代碼時,我們驚奇的發現,bw_RunWorkerCompleted這個回調函數居然是運行在UI線程中的,也就是說在這個方法中我們不用再使用Invoke和BeginInvoke調用winform中的控件了, 更讓我奇怪的是,如果是在ConsoleApplication中同樣運行這段代碼,那麼bw_RunWorkerCompleted輸出的線程id和主線程id就並不相同.

那麼BackgroundWorker到底是怎麼實現跨線程封送的呢?

閱讀一下這個類的代碼,我們發現他藉助了AsyncOperation.Post(SendOrPostCallback d, object arg)

在winform下使用這個函數,就可以使得由SendOrPostCallback定義被封送會UI線程,聰明的博友可以用這個方法來實現自己的BackgroundWorker.

繼續查看下去,發現關鍵在於AsyncOperation的syncContext字段,這是一個SynchronizationContext類型的對象,而這個對象的Post方法具體實現了封送,當我繼續查看

SynchronizationContext.Post方法時,裏面簡單的令人難以執行

public virtual void Post(SendOrPostCallback d, object state) { ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state); }
這是怎麼回事情呢,線程池本省並不具備線程封送的能力啊
聯想到在Winform程序和Console程序下程序的行爲是不同的,而且SynchronizationContext的Post方法是一個virtual方法,我猜測這個方法可能被繼承自他的類重寫了
查詢Msdn,果然發現在這個類有兩個子類,其中一個就是WindowsFormsSynchronizationContext,我們來看看這個類的Post方法
public override void Post(SendOrPostCallback d, object state) { if (this.controlToSendTo != null) { this.controlToSendTo.BeginInvoke(d, new object[] { state }); } }

哈哈,又是熟悉的beginInvoke,原來控制檯程序和Winform程序加載的SynchronizationContext是不同的,所以行爲才有所不同,通過簡單的測試,我們可以看到控制檯程序直接使用基類(SynchronizationContext),而winform程序使用這個WindowsFormsSynchronizationContext的Post方法把方法調用封送到控件的線程.

 總結:

同事這個類還提供了進度改變事件,允許用戶終止線程,功能全面,內部使用了線程池,能在一定成都上避免了大量線程的資源耗用問題,並通過SynchronizationContext解決了封送的問題,讓我們的回調事件代碼邏輯簡單清晰,推薦大家使用.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章