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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值