• BackgroundWorker实现原理


    实现原理

    在分析BackgroundWorker实现原理之前,需要了解一下在.NET Framework 2.0版本中新增加的两个类。AsyncOperationManager 类和AsyncOperation 类都位于System.ComponentModel 命名空间中,AsyncOperation类提供了对异步操作的生存期进行跟踪的功能,包括操作进度通知和操作完成通知,并确保在正确的线程或上下文中调 用客户端的事件处理程序。

    public void Post(SendOrPostCallback d,Object arg);

    public void PostOperationCompleted(SendOrPostCallback d,Object arg);

    通过在异步辅助代码中调用Post方法把进度和中间结果报告给用户,如果是取消异步任务或提示异步任务已完成,则通过调用 PostOperationCompleted方法结束异步操作的跟踪生命期。在PostOperationCompleted方法调用 后,AsyncOperation对象变得不再可用,再次访问将引发异常。在两个方法中都包含SendOrPostCallback委托参数,签名如下,

    public delegate void SendOrPostCallback(Object state);

    SendOrPostCallback 委托用来表示在消息即将被调度到同步上下文时要执行的回调方法。

    AsyncOperation 类看上去很强大,不过有开发人员反映该类的.NET 2.0版本存在Bug,在3.0及后面的版本微软是否进行过更新还需进一步考证。 笔者在控制台应用程序中进行测试,asyncOperation的Post方法递交的SendOrPostCallback 委托不一定是在控制台主线程执行,通过访问 System.Threading.Thread.CurrentThread.ManagedThreadId可以确认这一点,奇怪的是控制台程序未发 现运行异常,这个可能是控制台程序执行方式不同于窗体程序的原因。

    AsyncOperationManager 类为AsyncOperation对象的创建提供了便捷方式,通过CreateOperation方法可以创建多个AsyncOperation实例,实现对多个异步操作进行跟踪。

    BackgroundWorker 组件通过DoWork事件实现了在单独的线程上执行操作,其内部通过异步委托来完成,在BackgroundWorker类内部声明了 WorkerThreadStartDelegate 委托,并 定义了threadStart 成员变量,同时在构造函数中初始化threadStart。

    private delegate void WorkerThreadStartDelegate(object argument);
    private readonly WorkerThreadStartDelegate threadStart;
    public BackgroundWorker()
    {
    this.threadStart = new WorkerThreadStartDelegate(this.WorkerThreadStart);
    //…
    }

    BackgroundWorker 通过调用RunWorkerAsync 方法开始执行异步操作请求,并在方法体中调用threadStart.BeginInvoke方法实现异步调用。

    public void RunWorkerAsync(object argument)
    {
    if (this.isRunning)
    {
    throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerAlreadyRunning"));
    }
    this.isRunning = true;
    this.cancellationPending = false;
    this.asyncOperation = AsyncOperationManager.CreateOperation(null);
    this.threadStart.BeginInvoke(argument, null, null);
    }

    在threadStart 委托中指定的WorkerThreadStart方法将触发DoWork事件,使用者通过注册DoWork事件执行异步代码的操作,从下面的代码可以看出在DoWork事件处理程序中不能访问UI元素的原因。

    private void WorkerThreadStart(object argument)
    {
    object result = null;
    Exception error = null;
    bool cancelled = false;
    try
    {
    DoWorkEventArgs e = new DoWorkEventArgs(argument);
    this.OnDoWork(e);
    if (e.Cancel)
    {
    cancelled = true;
    }
    else
    {
    result = e.Result;
    }
    }
    catch (Exception exception2)
    {
    error = exception2;
    }
    RunWorkerCompletedEventArgs arg = new RunWorkerCompletedEventArgs(result, error, cancelled);
    this.asyncOperation.PostOperationCompleted(this.operationCompleted, arg);
    }

    在上述代码中,this.OnDoWork(e)方法产生DoWork事件,DoWork事件处理程序执行完成后会判断在事件处理程序中是否对 DoWorkEventArgs.Cancel 属性进行了设置,如果使用者调用了CancelAsync 方法那么DoWorkEventArgs.Cancel会被设置为true,事件处理程序正常执行完成时可以从 DoWorkEventArgs.Result得到执行结果,如果出现处理异常将扑获异常,所有需要的信息将包含在 RunWorkerCompletedEventArgs实例中,最后执行asyncOperation.PostOperationCompleted 方法产生RunWorkerCompleted 事件,因此在RunWorkerCompleted事件处理程序中可以获得取消操作、处理异常或处理结果的信息。

    类似于RunWorkerCompleted事件的发生机制,对于异步操作进度通知事件发生通过ReportProgress方法实现。

    public void ReportProgress(int percentProgress, object userState)
    {
    if (!this.WorkerReportsProgress)
    {
    throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerDoesntReportProgress"));
    }
    ProgressChangedEventArgs arg = new ProgressChangedEventArgs(percentProgress, userState);
    if (this.asyncOperation != null)
    {
    this.asyncOperation.Post(this.progressReporter, arg);
    }
    else
    {
    this.progressReporter(arg);
    }
    }

    调用者在DoWork事件处理程序中通过调用ReportProgress 方法进行进度汇报,其内部通过asyncOperation.Post方法产生ProgressChanged 事件,如果asyncOperation为null,那么就调用progressReporter方法产生事件,但是调用 progressReporter方法产生事件明显存在问题,因为这样产生的事件所在线程同DoWork事件为同一线程,ProgressChanged 事件处理程序也会执行在DoWork线程同一上下文中,因此在ProgressChanged事件处理程序中访问ProgressBar控件将出现“线程 间操作无效: 从不是创建控件“progressBar1”的线程访问它。”的异常。笔者认为这样的处理是组件的一个Bug,如果asyncOperation为 null,更好的处理方式是抛出异常或不做通知处理。值得一提的是,在控制台应用程序中测试调用progressReporter方法不会出现“线程间操 作无效”的异常。

    结合构造函数,下面的代码有助于进一步理解ProgressChanged事件和RunWorkerCompleted事件产生机制。

    public BackgroundWorker()
    {
     this.threadStart = new WorkerThreadStartDelegate(this.WorkerThreadStart);
     this.operationCompleted = new SendOrPostCallback(this.AsyncOperationCompleted);
    this.progressReporter = new SendOrPostCallback(this.ProgressReporter);
    }
    private void ProgressReporter(object arg)
    {
    this.OnProgressChanged((ProgressChangedEventArgs)arg);
    }
    private void AsyncOperationCompleted(object arg)
    {
    this.isRunning = false;
    this.cancellationPending = false;
    this.OnRunWorkerCompleted((RunWorkerCompletedEventArgs)arg);
    }

    最后,看一下RunWorkerAsync 方法和CancelAsync方法的实现。

    public void RunWorkerAsync(object argument)
    {
    if (this.isRunning)
    {
    throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerAlreadyRunning"));
    }
    this.isRunning = true;
    this.cancellationPending = false;
    this.asyncOperation = AsyncOperationManager.CreateOperation(null);
    this.threadStart.BeginInvoke(argument, null, null);
    }

    public void CancelAsync()
    {
    if (!this.WorkerSupportsCancellation)
    {
    throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerDoesntSupportCancellation"));
    }
    this.cancellationPending = true;
    }

    结束语

    BackgroundWorker 组件简化了基于事件的异步操作编程,根据其实现原理可进一步编写支持多任务的异步操作组件来更好的满足异步操作密集的应用开发需求。

    做个快乐的自己。
  • 相关阅读:
    蓝桥杯 2014本科C++ B组 六角填数 枚举排列
    蓝桥杯 2014本科C++ B组 地宫取宝 DFS+记忆化搜索
    埃及分数 IDA*
    优先队列详解(转载)
    HDU 1242 Rescue BFS+优先队列
    HDU 1627 Krypton Factor
    2018中国机器人大赛服务机器人专项赛赛后总结
    OpenMP使用体验报告(概述)
    写在归程路上——2018ROBOCUP机器人世界杯中国赛
    ROS编译工作区缺少cv_bridge的问题解决
  • 原文地址:https://www.cnblogs.com/Jessy/p/1885195.html
Copyright © 2020-2023  润新知