程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> BackgroundWorker原理剖析

BackgroundWorker原理剖析

編輯:C#入門知識

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved