BackgroundWorker類位於System.ComponentModel命名空間下,主要用來異步執行一個長時間的操作,然後,在完成事件中安全更新UI的控件屬性。UI中的控件是不允許非創建該控件的線程修改的。典型用法如下:
BackgroundWorker m_worker = new BackgroundWorker(); // 設置支持進度報告、異步取消功能,默認都為false m_worker.WorkerReportsProgress = true; m_worker.WorkerSupportsCancellation = true; // 綁定事件 m_worker.DoWork += m_worker_DoWork; m_worker.ProgressChanged += m_worker_ProgressChanged; m_worker.RunWorkerCompleted += m_worker_RunWorkerCompleted; void m_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled == true) { // 處理取消 return; } else if (e.Error != null) { // 處理異常 return; } // 在UI中顯示結果 // txtBox.Text = e.Result.ToString(); } void m_worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { //progressBar.Value = e.ProgressPercentage; } void m_worker_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker sendWorker = sender as BackgroundWorker; for (int i = 0; i < 100; i++) { // 做異步工作。。。。 // 報告進度 sendWorker.ReportProgress(i); // 請求取消工作內容 if (sendWorker.CancellationPending == true) { e.Cancel = true; return; } } // 可選,設置異步工作結果 e.Result = GetResultData(); }
它的實現原理最重要的只有兩點:
一點是用異步委托間接使用線程池執行長時間的操作;
另外一點是通過AsyncOperationManager和AsyncOperation對調用RunWorkerAsync的線程SynchronizationContext進行抽象;
BackgroundWorker的源碼參見 http://www.projky.com/dotnet/4.5.1/System/ComponentModel/BackgroundWorker.cs.html
首先從它的構造函數開始:
private delegate void WorkerThreadStartDelegate(object argument); private AsyncOperation asyncOperation = null; private readonly WorkerThreadStartDelegate threadStart; private readonly SendOrPostCallback operationCompleted; private readonly SendOrPostCallback progressReporter; public BackgroundWorker() { threadStart = new WorkerThreadStartDelegate(WorkerThreadStart); operationCompleted = new SendOrPostCallback(AsyncOperationCompleted); progressReporter = new SendOrPostCallback(ProgressReporter); }
定義了一個私有的委托類型WorkerThreadStartDelegate,以便於在該委托類型對象上直接調用BaginInvoke到線程池執行委托。SendOrPostCallback 是方便在UI線程(本質是調用RunWorkAsync時捕獲的當前線程同步上下文對象,為了容易理解,就叫它UI線程)上執行回調而創建的。而asyncOperation則通過Post方法在UI線程上異步來執行SendOrPostCallback委托。
在對DoWork添加事件後,需要調用RunWorkerAsync,有兩個重載,但我們只關注最後一個帶參數的:
public void RunWorkerAsync(object argument) { if (isRunning) { throw new InvalidOperationException(SR.GetString(SR.BackgroundWorker_WorkerAlreadyRunning)); } isRunning = true; cancellationPending = false; asyncOperation = AsyncOperationManager.CreateOperation(null); threadStart.BeginInvoke(argument, null, null); }
其實,asyncOperation = AsyncOperationManager.CreateOperation(null);這一行代碼,等同於下面的代碼:
if (SynchronizationContext.Current == null) { SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); } SynchronizationContext currentContext = SynchronizationContext.Current; asyncOperation = AsyncOperation.CreateOperation(null, currentContext)
簡單來說,就是獲得當前的SynchronizationContext的對象,如果不存在,則創建一個默認的(基於線程池實現的)。並讓asyncOperation擁有SynchronizationContext的引用。
在.NET中,有很多種SynchronizationContext的子類,比如Winform裡面的WindowsFormsSynchronizationContext類,WPF裡面的DispatcherSynchronizationContext類,ASP.NET裡面的AspNetSynchronizationContext類。重點是,當在Winform的UI線程中訪問SynchronizationContext.Current屬性,獲得的就是WindowsFormsSynchronizationContext的對象。
那麼,最終,AsyncOperation的Post方法,就是直接調用SynchronizationContext的Post方法,來實現在UI中回調的目的。
public void Post(SendOrPostCallback d, object arg) { VerifyNotCompleted(); VerifyDelegateNotNull(d); syncContext.Post(d, arg); }
還有一點,threadStart.BeginInvoke會用線程池中的線程執行類似如下的代碼:
object workerResult = null; Exception error = null; bool cancelled = false; try { DoWorkEventArgs doWorkArgs = new DoWorkEventArgs(argument); DoWorkEventHandler handler = (DoWorkEventHandler)(Events[doWorkKey]); if (handler != null) { handler(this, doWorkArgs); } if (doWorkArgs.Cancel) { cancelled = true; } else { workerResult = doWorkArgs.Result; } } catch (Exception exception) { error = exception; } RunWorkerCompletedEventArgs e = new RunWorkerCompletedEventArgs(workerResult, error, cancelled); asyncOperation.PostOperationCompleted(operationCompleted, e);
其中,對DoWork事件的聲明如下:
private static readonly object doWorkKey = new object(); public event DoWorkEventHandler DoWork{ add{ this.Events.AddHandler(doWorkKey, value); } remove{ this.Events.RemoveHandler(doWorkKey, value); } }
Events是從Component下派生來的protected EventHandlerList對象。
從BackgroundWorker的實現可以看出,它的實現是普遍性的,並不一定要用在Winform或者WPF中。
本文引用的源碼參考列表可以從扣丁格魯上查看。
http://www.projky.com/dotnet/4.5.1/System/ComponentModel/BackgroundWorker.cs.html
http://www.projky.com/dotnet/4.5.1/System/ComponentModel/AsyncOperation.cs.html
http://www.projky.com/dotnet/4.5.1/System/ComponentModel/AsyncOperationManager.cs.html
http://www.projky.com/dotnet/4.5.1/System/Threading/SynchronizationContext.cs.html
http://www.projky.com/dotnet/4.5.1/System/Windows/Forms/WindowsFormsSynchronizationContext.cs.html
http://www.projky.com/dotnet/4.5.1/System/Windows/Threading/DispatcherSynchronizationContext.cs.html
http://www.projky.com/dotnet/4.5.1/System/Web/AspNetSynchronizationContext.cs.html
關於基於事件的異步編程設計模式EAP更多參考請見http://msdn.microsoft.com/zh-cn/library/hkasytyf(v=vs.110).aspx