实现增强的异步任务执行组件

 
实现增强的异步任务执行组件
发布日期:2008-07-20 | 更新日期:2008-07-20
作者:郑佐
  
摘要:本文介绍如何在.NET中实现基于事件的异步模式组件。
  
下载与本文相关的MultiBackgroundWorkerSample示例代码。
  
本页内容
  
在应用程序中,可能会遇到一些执行耗时的功能操作,比如数据下载、复杂计算及数据库事务等,一般这样的功能会在单独的线程上实现,避免出现用户界面长时间无响应情况。在.NET 2.0中,FCL提供了BackgroundWorker组件来方便的实现这些功能要求,该组件在功能上的确很吸引人,这篇文章对BackgroundWorker组件进行了比较详细的介绍。本文将实现一个增强的BackgroundWorker组件,支持基于事件的多任务异步操作。
  
BackgroundWorker组件采用基于事件的异步模式简化了多线程操作编程,不过其不能对多个异步任务生命期进行管理,因此开发人员可能会通过使用多个BackgroundWorker实例来应对异步操作密集的情况。MultiBackgroundWorker组件解决多任务的问题,使得单个实例对多个异步任务的生命期进行集中管理,对于每个任务同样提供异步任务操作请求,异步任务执行进度汇报以及异步任务结束通知。
  
异步任务操作请求。MultiBackgroundWorker组件提供了RunWorkerAsync方法来开始一个异步操作的请求,该方法需要一个参数来唯一标识新的异步任务,如果任务执行过程中需要用到相关信息数据可以通过第二个参数传入。
public virtual void RunWorkerAsync(object taskId, object argument);
在RunWorkerAsync方法调用后,MultiBackgroundWorker会生成一个新的异步任务,并对其生命周期进行管理,同时触发DoWork事件。
public event MultiDoWorkEventHandler DoWork;
在事件处理程序中通过MultiDoWorkEventArgs对象获取参数信息和任务标识。类似于BackgroundWorker组件,调用程序注册DoWork事件并在该事件处理程序中编写异步处理逻辑代码。DoWork事件处理程序执行的线程不同于调用RunWorkerAsync方法的线程,因此,调用线程是UI线程时,在DoWork事件处理程序中就不能编写访问UI元素的代码,而实际在编写WinForm应用程序时调用线程基本上UI线程,值得注意。
  
异步任务执行进度汇报。同BackgroundWorker组件一样,MultiBackgroundWorker同样提供了ReportProgress方法。
public void ReportProgress(object taskId, int progressPercentage, object userState);
方法的第一个参数为进度汇报对应的任务标识,第二个参数为已完成的异步任务操作进度所占的百分比,取值范围从0到100,最后一个参数为用户自定义信息。在调用ReportProgress方法后将激发ProgressChanged事件。
public event MultiProgressChangedEventHandler ProgressChanged;
调用程序在对应的事件处理程序中可以访问MultiProgressChangedEventArgs对象获取进度信息并显示到界面上。
  
异步任务结束通知。如果调用程序需要获得每个异步任务结束通知以便执行一些后续代码,那么可以通过注册RunWorkerCompleted事件来实现。
public event MultiRunWorkerCompletedEventHandler RunWorkerCompleted;
异步任务人为取消、异常终止或正常结束都会触发RunWorkerCompleted事件,并且在该事件处理程序中,通过MultiRunWorkerCompletedEventArgs对象获取处理结果信息。在事件处理程序中,一般遵循“先判断异步任务是否异常结束,接着判断是否执行了取消操作,最后访问处理结果”的步骤。
  
MultiBackgroundWorker组件提供了另外两个方法以便更好的工作。CancelAsync方法用来取消异步任务的执行,该方法需要提供任务标识以便让组件知道需要对哪个任务执行取消操作。TaskCanceled方法判断异步任务是否已经取消。
public void CancelAsync(object taskId);
public bool TaskCanceled(object taskId);
  
本文通过一个简单的WinForm程序来演示MultiBackgroundWorker组件如何简化多任务异步模式编程。
MultiBackgroundWorkerSample应用程序实现了多任务求和计算功能,用户可以在界面上添加任务,取消任务和删除任务,对于正在执行的任务可以查看其处理进程以及最后处理结果。为更好的演示多任务异步模式,程序界面显示了主线程Id和各个任务线程Id。
MultiBackgroundWorker.jpg
图1:应用程序界面
上图展示了程序主界面,窗体上包含4个控件。ListView显示了任务的执行情况,针对每项任务显示任务Id、求和计算数值、处理进程、任务线程Id以及处理结果。三个按钮控件用来处理任务开始、任务取消和任务移除。
  
MultiBackgroundWorker组件在MainForm类的构造函数中被实例化,并注册相关事件。
multiBackgroundWorker1 = new MultiBackgroundWorker();
multiBackgroundWorker1.DoWork +=
new MultiDoWorkEventHandler(multiBackgroundWorker1_DoWork);
multiBackgroundWorker1.ProgressChanged +=
new MultiProgressChangedEventHandler(multiBackgroundWorker1_ProgressChanged);
multiBackgroundWorker1.RunWorkerCompleted +=
new MultiRunWorkerCompletedEventHandler(multiBackgroundWorker1_RunWorkerCompleted);
  
新任务通过Start按钮创建,在按钮事件处理程序中,程序随机生成一个50到100之间的数值,作为求和计算的最大加数。新的任务通过生成Guid字符串来表示其唯一性,在任务初始化后作为ListViewItem项添加到ListView进行显示,最后MultiBackgroundWorker组件执行RunWorkerAsync方法启动异步操作请求。
private void startButton_Click(object sender, EventArgs e)
{
    Random rand = new Random();
    int testNumber = rand.Next(50,100);
    string taskId = Guid.NewGuid().ToString();
    this.AddListViewItem(taskId, testNumber);
    this.multiBackgroundWorker1.RunWorkerAsync(taskId, testNumber);
}
  
在组件的RunWorkerAsync方法调用后,DoWork事件激发,在事件处理程序中具体实现了异步操作任务,可以注意到从DoWork事件处理程序到辅助方法代码都没有对UI元素进行访问。
void multiBackgroundWorker1_DoWork(object sender, MultiDoWorkEventArgs e)
{
    int n = (int)e.Argument;
    object taskId = e.TaskId;
    e.Result = ComputeAdd(n, taskId, (MultiBackgroundWorker)sender, e);
}
MultiDoWorkEventArgs参数包含了调用程序关心的信息,程序通过ComputeAdd辅助方法进行实际的求和计算,由于该运算过程很快,因此通过Thread.Sleep方法来降低其计算速度。在计算的循环体中,通过worker.TaskCanceled方法判断当前任务是否发出了取消请求,如果该任务已经被取消那么退出计算过程。
private long ComputeAdd(int n, object taskId, MultiBackgroundWorker worker, MultiDoWorkEventArgs e)
{
    long result = 0;
for (int i = 1; i <= n; i++)
    {
        if (worker.TaskCanceled(taskId))
        {
            e.Cancel = true;
            break;
        }
        result += i;
        Thread.Sleep(1000);
        int progressPercentage = (int)((float)i / (float)n * 100);
        worker.ReportProgress(taskId, progressPercentage,
Thread.CurrentThread.ManagedThreadId);
    }
    return result;
}
在辅助方法代码中,程序在循环体内执行了worker.ReportProgress方法产生ProgressChanged事件来向调用程序公开访问接口,调用程序通过MultiProgressChangedEventArgs对象获取任务进度信息并更新信息到ListView进行显示,UserState属性包含了托管线程Id信息。
void multiBackgroundWorker1_ProgressChanged(object sender, MultiProgressChangedEventArgs e)
{
    UpdateListViewItem((string)e.TaskId, e.ProgressPercentage, (int)e.UserState);
}
在每一个任务执行完成、出现异常或被取消后MultiBackgroundWorker组件都会激发RunWorkerCompleted事件,调用程序访问对应的事件处理程序获得处理结果。调用程序先判断异步任务是否异常结束,然后判断是否执行了取消操作,最后访问处理结果,并显示结果到ListView。
void multiBackgroundWorker1_RunWorkerCompleted(object sender, MultiRunWorkerCompletedEventArgs e)
{
    string taskId = (string)e.TaskId;
    ListViewItem item;
    if (e.Error != null)
    {
        item = UpdateListViewItem(taskId, "Error");
    }
    else if (e.Cancelled)
    {
        item = UpdateListViewItem(taskId, "Canceled");
    }
    else
    {
        string result = ((long)e.Result).ToString(CultureInfo.CurrentCulture.NumberFormat);
        item = UpdateListViewItem(taskId, result);
    }
    item.Tag = null;//设置TaskId为空。
}
  
另外,程序实现了对正在执行的任务进行取消的功能,通过组件的CancelAsync方法发出取消请求。
private void cancelButton_Click(object sender, EventArgs e)
{
    foreach (ListViewItem lvi in this.listView1.SelectedItems)
    {
        if (lvi.Tag != null)
        {
            string taskId = (string)lvi.Tag;
            this.multiBackgroundWorker1.CancelAsync(taskId);
            lvi.Selected = false;
        }
    }
    cancelButton.Enabled = false;
}
  
MultiBackgroundWorker组件的实现原理同BackgroundWorker相同,使用基于事件的异步模式实现多线程编程。由于这篇文章对BackgroundWorker组件的实现原理进行了比较多的分析,因此本节只对组件的一些关键实现进行讲解。
  

MultiBackgroundWorker组件部分功能的实现使用了.NET 2.0中新增的类型,因此先来了解一下这些新类型。在.NET Framework 2.0版本中FCL在System.ComponentModel 命名空间下增加了AsyncOperationManager 类和AsyncOperation 类来辅助异步操作编程。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 委托用来表示在消息即将被调度到同步上下文时要执行的回调方法。AsyncOperationManager类为AsyncOperation对象的创建提供了便捷方式,通过CreateOperation方法可以创建多个AsyncOperation实例,实现对多个异步操作进行跟踪。
  
那么MultiBackgroundWorker组件如何实现对多个异步任务的生命周期进行管理?在MultiBackgroundWorker类内部定义了一个HybridDictionary容器类来保存各个任务的引用,在有新任务产生时容器会增加一项针对新任务的引用,在任务处理结束后容器会移除该任务的引用信息。由于容器需要被多个线程访问,因此必须是线程安全的。
private HybridDictionary userStateToLifetime = new HybridDictionary();
组件通过RunWorkerAsync方法为每个任务创建对应的AsyncOperation实例保存到任务容器中,并通过异步委托启动异步操作。
public virtual void RunWorkerAsync(object taskId, object argument)
{
    AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId);
    lock (userStateToLifetime.SyncRoot)
    {
        if (userStateToLifetime.Contains(taskId))
        {
            throw new ArgumentException("Task ID parameter must be unique");
        }
        userStateToLifetime[taskId] = asyncOp;
    }
    threadStart.BeginInvoke(taskId, argument, null, null);
}
MultiBackgroundWorker类内部定义了WorkerThreadStartDelegate委托,threadStart是该委托变量。
private delegate void WorkerThreadStartDelegate(object taskId, object argument);
private readonly WorkerThreadStartDelegate threadStart;
在构造函数中,该委托变量通过指定WorkerThreadStart方法被实例化
this.threadStart = new WorkerThreadStartDelegate(this.WorkerThreadStart);
WorkerThreadStart方法可以说是整个组件的核心,该方法在内部通过调用OnDoWork方法触发DoWork事件,在DoWork事件处理程序执行完成后,通过检查MultiDoWorkEventArgs对象Cancel属性来判断用户是否进行取消操作的请求;如果Cancel属性为False,那么获取处理结果;如果DoWork事件处理程序执行过程中出现异常,那么扑获异常。在该方法中,任务对应的AsyncOperation对象被从任务容器中移除,标志着该任务生命周期的结束。方法体的最后调用AsyncOperation 的PostOperationCompleted方法触发RunWorkerCompleted事件,可以看到MultiRunWorkerCompletedEventArgs对象包含了任务ID、任务执行结果、错误信息和取消标志。
private void WorkerThreadStart(object taskId, object argument)
{
    object result = null;
    Exception error = null;
    bool cancelled = false;
    AsyncOperation asyncOp = GetAsyncOperation(taskId);
    try
    {
        MultiDoWorkEventArgs e = new MultiDoWorkEventArgs(taskId, argument);
        this.OnDoWork(e);
        if (e.Cancel)//需要标示完成还是退出
        {
            cancelled = true;
        }
        else
        {
            result = e.Result;
        }
    }
    catch (Exception ex)
    {
        error = ex;
    }
    if (TaskCanceled(taskId) == false)
    {
        lock (userStateToLifetime.SyncRoot)
        {
            userStateToLifetime.Remove(taskId);
        }
    }
MultiRunWorkerCompletedEventArgs ee =
new MultiRunWorkerCompletedEventArgs(taskId, result, error, cancelled);
    asyncOp.PostOperationCompleted(onAsyncOperationCompletedDelegate, ee);
}
  
对于异步任务进度汇报功能的实现,组件的ReportProgress方法在内部调用AsyncOperation的Post方法触发ProgressChanged事件来完成。
public void ReportProgress(object taskId, int progressPercentage, object userState)
{
    AsyncOperation asyncOp = GetAsyncOperation(taskId);
    if (asyncOp != null)
    {
        ProgressChangedEventArgs e =
new MultiProgressChangedEventArgs(taskId, progressPercentage, userState);
        asyncOp.Post(this.onProgressReportDelegate, e);
    }
}
onProgressReportDelegate为SendOrPostCallback委托对象,在构造函数中进行初始化,
this.onProgressReportDelegate = new SendOrPostCallback(ProgressReport);
  
通过下面的两个辅助方法最后产生ProgressChanged事件。
private void ProgressReport(object state)
{
    MultiProgressChangedEventArgs e = state as MultiProgressChangedEventArgs;
   OnProgressChanged(e);
}
protected virtual void OnProgressChanged(MultiProgressChangedEventArgs e)
{
    if (ProgressChanged != null)
    {
        ProgressChanged(this, e);
    }
}
  
DoWork事件处理程序的线程是异步任务线程,而ProgressChanged事件处理程序的线程是实例化MultiBackgroundWorker组件的线程,AsyncOperation通过Post方法神奇的解决了该问题。
同样,RunWorkerCompleted事件也通过下面的两个辅助方法得到触发,线程问题通过AsyncOperation的PostOperationCompleted方法解决。
private void MultiRunWorkerCompleted(object operationState)
{
MultiRunWorkerCompletedEventArgs e =
operationState as MultiRunWorkerCompletedEventArgs;
    OnRunWorkerCompleted(e);
}
protected virtual void OnRunWorkerCompleted(MultiRunWorkerCompletedEventArgs e)
{
    if (RunWorkerCompleted != null)
    {
        RunWorkerCompleted(this, e);
    }
}
  
最后我们来看一下任务状态查询和任务取消方法的实现。对于任务状态的查询,只需根据TaskId查询一下内部容器,如果容器中没有包含任务对应的AsyncOperation对象说明任务已经结束或者取消,否则认为任务还在执行中;同样,要取消一个异步任务,只需要根据TaskId判断容器是否存在对应的AsyncOperation对象,如果存在就从容器中移除,从而实现任务取消请求。
public bool TaskCanceled(object taskId)
{
    return (userStateToLifetime[taskId] == null);
}
  
public void CancelAsync(object taskId)
{
    AsyncOperation asyncOp = userStateToLifetime[taskId] as AsyncOperation;
    if (asyncOp != null)
    {
        lock (userStateToLifetime.SyncRoot)
        {
            userStateToLifetime.Remove(taskId);
        }
    }
}
  
MultiBackgroundWorker组件简化了基于事件的多任务异步操作编程,由于功能比较清晰,作者没有为组件实现设计时支持,因为不是很需要。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值