好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

基于事件的异步编程模式(EMP)

基于事件的异步编程模式(EMP)

基于事件的异步编程模式(EMP)

=============C#.Net 篇目录==============

上一篇,我给大家介绍了“ .NET1.0中基于IAsyncResult设计模式的异步编程模型(APM) ”,它使用System.Threading命名空间的类来构造多线程应用程序。然而要想有效地使用这些工具类,需要有丰富的使用多线程软件工程的经验。对于相对简单的多线程应用程序,BackgroundWorker组件提供了一个简单的解决方案。对于更复杂的异步应用程序,可以考虑实现一个符合基于事件的异步模式的类。

使用支持此模式的类,您将能够:

1)   异步执行耗时的任务。

2)   获得进度报告和增量结果。

3)   支持耗时任务的取消。

4)   获得任务的结果值或异常信息。

5)   更复杂:支持同时执行多个异步操作、进度报告、增量结果、取消操作、返回结果值或异常信息。

 

 

源码下载: 异步编程:基于事件的异步模型(EAP).rar

 

为了实现基于事件的异步模式,我们必须先理解两个重要的帮助器类:

 

AsyncOperationManager和AsyncOperation

    AsyncOperationManager类和AsyncOperation类是System.ComponentModel命名空间为我们提供了两个重要帮助器类。在基于事件的异步模式封装标准化的异步功能中,它确保你的异步操作支持在各种应用程序模型(包括ASP.NET、控制台应用程序和 Windows 窗体应用程序)的适当“线程或上下文”调用客户端事件处理程序。

AsyncOperationManager类和AsyncOperation类的API如下:

// 为支持异步方法调用的类提供并发管理。此类不能被继承。

public   static   class   AsyncOperationManager

{

     // 获取或设置用于异步操作的同步上下文。

     public   static   SynchronizationContext SynchronizationContext { get ; set ; }

 

     // 返回可用于对特定异步操作的持续时间进行跟踪的AsyncOperation对象。

     // 参数:userSuppliedState:

     //     一个对象,用于使一个客户端状态(如任务 ID)与一个特定异步操作相关联。

     public   static   AsyncOperation CreateOperation( object   userSuppliedState)

     {

         return   AsyncOperation.CreateOperation(userSuppliedState,SynchronizationContext);

     }

}

 

// 跟踪异步操作的生存期。

public   sealed   class   AsyncOperation

{

     // 构造函数

     private   AsyncOperation( object   userSuppliedState, SynchronizationContext syncContext);

     internal   static   AsyncOperation CreateOperation( object   userSuppliedState

                                             , SynchronizationContext syncContext);

 

     // 获取传递给构造函数的SynchronizationContext对象。

     public   SynchronizationContext SynchronizationContext { get ; }

     // 获取或设置用于唯一标识异步操作的对象。

     public   object   UserSuppliedState { get ; }

 

     // 在各种应用程序模型适合的线程或上下文中调用委托。

     public   void   Post(SendOrPostCallback d, object   arg);

     // 结束异步操作的生存期。

     public   void   OperationCompleted();

     // 效果同调用 Post() + OperationCompleted() 方法组合

     public   void   PostOperationCompleted(SendOrPostCallback d, object   arg);

}

    先分析下这两个帮助器类:

AsyncOperationManager是静态类。静态类是密封的,因此不可被继承。倘若从静态类继承会报错“静态类必须从 Object 派生”。(小常识,以前以为密封类就是 sealed 关键字) AsyncOperationManager为支持异步方法调用的类提供并发管理,该类可正常运行于 .NET Framework 支持的所有应用程序模式下。 AsyncOperation实例提供对特定异步任务的生存期进行跟踪。可用来处理任务完成通知,还可用于在不终止异步操作的情况下发布进度报告和增量结果(这种不终止异步操作的处理是通过AsyncOperation的 Post() 方法实现)。 AsyncOperation类有一个私有的构造函数和一个内部CreateOperation() 静态方法。由AsyncOperationManager类调用AsyncOperation.CreateOperation() 静态方法来创建AsyncOperation实例。 AsyncOperation类是通过SynchronizationContext类来实现在各种应用程序的适当“线程或上下文”调用客户端事件处理程序。

// 提供在各种同步模型中传播同步上下文的基本功能。

public   class   SynchronizationContext

{

     // 获取当前线程的同步上下文。

     public   static   SynchronizationContext Current { get ; }

 

     // 当在派生类中重写时,响应操作已开始的通知。

     public   virtual   void   OperationStarted();

     // 当在派生类中重写时,将异步消息调度到一个同步上下文。

     public   virtual   void   Post(SendOrPostCallback d, object   state);

     // 当在派生类中重写时,响应操作已完成的通知。

     public   virtual   void   OperationCompleted();

     ……

}

a)   在AsyncOperation构造函数中调用SynchronizationContext的OperationStarted() ;

b)       在AsyncOperation的 Post() 方法中调用SynchronizationContext的Post() ;

c)   在AsyncOperation的OperationCompleted()方法中调用SynchronizationContext的OperationCompleted();

SendOrPostCallback委托签名:

    // 表示在消息即将被调度到同步上下文时要调用的方法。

public delegate void SendOrPostCallback(object state);

   

基于事件的异步模式的特征

基于事件的异步模式可以采用多种形式,具体取决于某个特定类支持操作的复杂程度:

1)   最简单的类可能只有一个 ***Async 方法 和一个对应的 ***Completed  事件 ,以及这些方法的同步版本。

2)   复杂的类可能有若干个 ***Async方法,每种方法都有一个对应的 ***Completed 事件,以及这些方法的同步版本。

3)   更复杂的类还可能为每个异步方法支持取消(CancelAsync()方法)、进度报告和增量结果(ReportProgress() 方法+ProgressChanged事件)。

4)   如果您的类支持多个异步方法,每个异步方法返回不同类型的数据,您应该:

a)   将您的增量结果报告与您的进度报告分开。

b)   使用适当的EventArgs为每个异步方法定义一个单独的 ***ProgressChanged事件以处理该方法的增量结果数据。

5)   如果类不支持多个并发调用,请考虑公开IsBusy属性。

6)   如要异步操作的同步版本中有 Out 和 Ref 参数,它们应做为对应 ***CompletedEventArgs的一部分,eg:

public   int   MethodName( string   arg1, ref   string   arg2, out   string   arg3);

 

public   void   MethodNameAsync( string   arg1, string   arg2);

public   class   MethodNameCompletedEventArgs : AsyncCompletedEventArgs

{

     public   int   Result { get ; };

     public   string   Arg2 { get ; };

     public   string   Arg3 { get ; };

}

如果你的组件要支持多个异步耗时的任务并行执行。那么:

1)   为***Async方法多添加一个userState对象参数(此参数应当始终是***Async方法签名中的最后一个参数),用于跟踪各个操作的生存期。

2)   注意要在你构建的异步类中维护一个userState对象的集合。使用 lock 区域保护此集合,因为各种调用都会在此集合中添加和移除userState对象。

3)   在***Async方法开始时调用AsyncOperationManager.CreateOperation并传入userState对象,为每个异步任务创建AsyncOperation对象,userState存储在AsyncOperation的UserSuppliedState属性中。在构建的异步类中使用该属性标识取消的操作,并传递给CompletedEventArgs和ProgressChangedEventArgs参数的UserState属性来标识当前引发进度或完成事件的特定异步任务。

4)   当对应于此userState对象的任务引发完成事件时,你构建的异步类应将AsyncCompletedEventArgs.UserState对象从集合中删除。

注意:

1)   确保 ***EventArgs类特定于***方法。即当使用 ***EventArgs类时,切勿要求开发人员强制转换类型值。

2)   确保始终引发方法名称Completed 事件。成功完成、异常或者取消时应引发此事件。任何情况下,应用程序都不应遇到这样的情况:应用程序保持空闲状态,而操作却一直不能完成。

3)   确保可以捕获异步操作中发生的任何异常并将捕获的异常指派给 Error 属性。

4)   确保 ***CompletedEventArgs 类将其成员公开为只读属性而不是字段,因为字段会阻止数据绑定。eg:public MyReturnType Result { get; }

5)   在构建 ***CompletedEventArgs 类属性时,通过this.RaiseExceptionIfNecessary() 方法确保属性值被正确使用。Eg:

private   bool   isPrimeValue;

public   bool   IsPrime

{

     get

     {

         RaiseExceptionIfNecessary();

         return   isPrimeValue;

     }

}

所以,在***Completed事件处理程序中,应当总是先检查 ***CompletedEventArgs.Error 和 ***CompletedEventArgs.Cancelled 属性,然后再访问RunWorkerCompletedEventArgs.Result属性。

 

BackgroundWorker组件

    System.ComponentModel命名空间的BackgroundWorker组件为我们提供了一个简单的多线程应用解决方案,它允许你在单独的线程上运行耗时操作而不会导致用户界面的阻塞。但是,要注意它同一时刻只能运行一个异步耗时操作(使用IsBusy属性判定),并且不能跨AppDomain边界进行封送处理(不能在多个AppDomain中执行多线程操作)。

BackgroundWorker组件 相应的EventArgs类 BackgroundWorker示例

public   class   BackgroundWorker : Component

{

     public   BackgroundWorker();

 

     // 获取一个值,指示应用程序是否已请求取消后台操作。

     public   bool   CancellationPending { get ; }

     // 获取一个值,指示BackgroundWorker是否正在运行异步操作。

     public   bool   IsBusy { get ; }

     // 获取或设置一个值,该值指示BackgroundWorker能否报告进度更新。

     public   bool   WorkerReportsProgress { get ; set ; }

     // 获取或设置一个值,该值指示BackgroundWorker是否支持异步取消。

     public   bool   WorkerSupportsCancellation { get ; set ; }

 

     // 调用RunWorkerAsync() 时发生。

     public   event   DoWorkEventHandlerDoWork;

     // 调用ReportProgress(System.Int32) 时发生。

     public   event   ProgressChangedEventHandlerProgressChanged;

     // 当后台操作已完成、被取消或引发异常时发生。

     public   event   RunWorkerCompletedEventHandlerRunWorkerCompleted;

 

     // 请求取消挂起的后台操作。

     public   void   CancelAsync();

     // 引发ProgressChanged事件。percentProgress:范围从 0% 到 100%

     public   void   ReportProgress( int   percentProgress);

     // userState:传递到RunWorkerAsync(System.Object) 的状态对象。

     public   void   ReportProgress( int   percentProgress, object   userState);

     // 开始执行后台操作。

     public   void   RunWorkerAsync();

     // 开始执行后台操作。argument:传递给DoWork事件的DoWorkEventArgs参数。

     public   void   RunWorkerAsync( object   argument);

}

///1)   System.EventArgs基类

     // System.EventArgs是包含事件数据的类的基类。

     public   class   EventArgs

     {

         // 表示没有事件数据的事件。

         public   static   readonly   EventArgs Empty;

         public   EventArgs();

     }

 

///2)   DoWorkEventArgs类

     // 为可取消的事件提供数据。

     public   class   CancelEventArgs : EventArgs

     {

         public   CancelEventArgs();

         public   CancelEventArgs( bool   cancel);

         // 获取或设置指示是否应取消事件的值。

         public   bool   Cancel { get ; set ; }

     }

     // 为DoWork事件处理程序提供数据。

     public   class   DoWorkEventArgs : CancelEventArgs

     {

         public   DoWorkEventArgs( object   argument);

 

         // 获取表示异步操作参数的值。

         public   object   Argument { get ; }

         // 获取或设置表示异步操作结果的值。

         public   object   Result { get ; set ; }

     }

 

///3)   ProgressChangedEventArgs类

     // 为ProgressChanged事件提供数据。

     public   class   ProgressChangedEventArgs : EventArgs

     {

         public   ProgressChangedEventArgs( int   progressPercentage, object   userState);

 

         // 获取异步任务的进度百分比。

         public   int   ProgressPercentage { get ; }

         // 获取唯一的用户状态。

         public   object   UserState { get ; }

     }

 

///4)   RunWorkerCompletedEventArgs类

     // 为MethodNameCompleted事件提供数据。

     public   class   AsyncCompletedEventArgs : EventArgs

     {

         public   AsyncCompletedEventArgs();

         public   AsyncCompletedEventArgs(Exception error, bool   cancelled, object   userState);

 

         // 获取一个值,该值指示异步操作是否已被取消。

         public   bool   Cancelled { get ; }

         // 获取一个值,该值指示异步操作期间发生的错误。

         public   Exception Error { get ; }

         // 获取异步任务的唯一标识符。

         public   object   UserState { get ; }

 

         // 访问 AsyncCompletedEventArgs 及其派生类的属性前调用此方法

         protected   void   RaiseExceptionIfNecessary()

         {

             if   ( this .Error != null )

             {

                 throw   new   TargetInvocationException(……);

             }

             if   ( this .Cancelled)

             {

                 throw   new   InvalidOperationException(……);

             }

         }

     }

     public   class   RunWorkerCompletedEventArgs : AsyncCompletedEventArgs

     {

         public   RunWorkerCompletedEventArgs( object   result, Exception error, bool   cancelled);

 

         // 获取表示异步操作结果的值。

         public   object   Result { get ; }

         // 获取表示用户状态的值。

         public   object   UserState { get ; }

     }

示例代码中包含了BackgroundWorker源代码及对应的使用示例 ,这里不粘贴代码了,会导致篇幅更大。来个示例截图吧:

 

 

示例分析:

1)   首先我们为BackgroundWorker组件注册DoWork(异步操作)、ProgressChanged(进度报告) 和RunWorkCompleted(完成通知)事件;

2)   设置WorkerSupportsCancellation和WorkerReportsProgress属性为true,以声明组件支持取消操作和进度报告;

3)   使用RunWorkerAsync() 开启异步操作,通过IsBusy属性判断是否已经有异步任务在执行;

4)   使用CancelAsync() 方法取消异步操作,但要注意:

a)   它仅仅是将BackgroudWorker.CancellationPending属性设置为true。需要在具体DoWork事件中不断检查BackgroudWorker.CancellationPending来设置DoWorkEventArgs的Cancel属性。

b)   DoWork事件处理程序中的代码有可能在发出取消请求时完成其工作,轮询循环可能会错过设置为 true 的CancellationPending属性。在这种情况下,即使发出了取消请求,RunWorkerCompleted事件处理程序中RunWorkerCompletedEventArgs的 Cancelled 标志也不会设置为 true。这种情况被称作争用状态。(可以通过直接监控组件的CancellationPending属性,来做判断)

5)   确保在DoWork事件处理程序中不操作任何用户界面对象。而应该通过ProgressChanged和RunWorkerCompleted事件与用户界面进行通信。

因为RunWorkerAsync() 是通过委托的BeginInvoke() 引发的DoWork事件,即DoWork事件的执行线程已不是创建控件的线程(我在 《异步编程:异步编程模型 (APM)》 中介绍了几种夸线程访问控件的方式)。而ProgressChanged和RunWorkerCompleted事件是通过帮助器类AsyncOperation的 Post() 方法使其调用发生在合适的“线程或上下文”中。

 

自定义基于事件的异步组件

    刚才我们介绍了BackgroundWorker组件,但是这个组件在一个时刻只能开启一个异步操作,那如果我们要想同时支持多个异步操作、进度报告、增量结果、取消和返回结果值或异常信息该怎么办呢?对的,我们可以为自己定义一个基于事件的异步组件。

    我直接引用MSDN上的 一则计算质数的异步组件示例,请从我提供的示例代码中获取 。

质数算法:埃拉托色尼筛法

eg:判断n是否为质数

1、1和0既非素数也非合数;

2、将2和3加入质数集合primes;从n=5开始,通过 n+=2 来跳过所有偶数;

3、循环集合primes中的质数并将其做为n的因子,能整除的为合数;

4、若不能整除,则继续循步骤3直到“因子的平方>n”,即可判断n为质数,并将其加入到集合primes。

 

来个示例截图吧:

 

 

示例分析:(组件名:PrimeNumberCalculator)

首先我们为PrimeNumberCalculator组件注册ProgressChanged(进度报告) 和CalculatePrimeCompleted(完成通知)事件; 使用CalculatePrimeAsync(intnumberToTest, object taskId)开启异步任务,注意我们需要传递一个唯一标识Guid taskId = Guid.NewGuid();用于标识取消的操作,并传递给CompletedEventArgs和ProgressChangedEventArgs参数的UserState属性来标识当前引发进度或完成事件的特定异步任务; 取消操作CancelAsync(object taskId),只是将taskId对应的AsyncOperation实例移除内部任务集合,耗时操作通过判断taskId是否存在于集合来判断其是否被取消;

 

 

此文到此结束,通过此博文我们认识到:

1)   基于事件的异步编程是通过AsyncOperationManager类和AsyncOperation类两个帮助器类确保你的异步操作支持在各种应用程序模型(包括 ASP.NET、控制台应用程序和 Windows 窗体应用程序)的适当“线程或上下文”调用访问控件;

2)   BackgroundWorker组件构建、使用和缺点。

3)   展现如何构建一个基于事件的异步组件,并且支持多个异步操作的并行运行

 

感谢大家的观赏,如本文对你有帮助还请多帮推荐支持下……

 

 

 

参考: MSDN

 


作者: 滴答的雨  
出处: http://www.cnblogs.com/heyuquan/  
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 

欢迎园友讨论下自己的见解,及推荐更好资料。 
本文如对读者有帮助,还请多帮  下此文。 
谢谢!!!  (  )

 

分类:  C#.Net 篇

标签:  异步编程

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于基于事件的异步编程模式(EMP)的详细内容...

  阅读:74次