zoukankan      html  css  js  c++  java
  • BackgroundWorker原理剖析

    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

  • 相关阅读:
    MySQL慢查询日志总结
    SQL Server 关于列的权限控制
    Oracle global database name与db link的纠缠关系
    TCP Provider The semaphore timeout period has expired
    SQL SERVER 中如何用脚本管理作业
    Unable to determine if the owner (DomainUserName) of job JOB_NAME has server access
    TNS-12535: TNS:operation timed out案例解析
    ORA-12154 & TNS-03505 案例分享
    MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间
    查看数据库表的数据量和SIZE大小的脚本修正
  • 原文地址:https://www.cnblogs.com/ProJKY/p/backgroundWorkerImp.html
Copyright © 2011-2022 走看看