基于事件的异步编程

基于事件的异步编程

基于事件的异步模式具有多线程应用程序的优点,同时隐藏了多线程设计中固有的许多复杂问题。 使用支持此模式的类,你将能够:

  • “在后台”执行耗时任务(例如下载和数据库操作),但不会中断你的应用程序。
  • 同时执行多个操作,每个操作完成时都会接到通知。
  • 等待资源变得可用,但不会停止(“阻止”)你的应用程序。
  • 使用熟悉的事件和委托模型与挂起的异步操作通信。

支持基于事件的异步模式的类将具有一个或多个命名为 MethodNameAsync 的方法。 这些方法可能会创建同步版本的镜像,这些同步版本会在当前线程上执行相同的操作。 该类还可能具有 MethodNameCompleted 事件,并且可能会具有 MethodNameAsyncCancel(或只是 CancelAsync)方法。

PictureBox 是一个支持基于事件的异步模式的典型组件。 你可以通过调用其 Load 方法来同步下载图像,但是如果图像很大,或者网络连接很慢,应用程序将停止响应,直到下载操作完成并且对 Load 的调用返回后才会继续执行。

如果你想要应用程序在加载图像时保持运行,你可以调用 LoadAsync 方法,处理 LoadCompleted 事件,这与你处理任何其他事件一样。 调用 LoadAsync 方法时,你的应用程序将继续运行,而下载操作将在另一个线程上(“在后台”)继续。 图像加载操作完成时,将调用你的事件处理程序,并且你的事件处理程序可以检查 AsyncCompletedEventArgs 参数以确定该下载是否已成功完成。
基于事件的异步模式要求异步操作可以取消,并且 PictureBox 控件使用其 CancelAsync 方法支持此要求。 调用 CancelAsync 会提交一个停止挂起的下载的请求,任务取消时会引发 LoadCompleted 事件。

基于事件的异步模式示例

public class AsyncExample  
{  
    // Synchronous methods.  
    public int Method1(string param);  
    public void Method2(double param);  
  
    // Asynchronous methods.  
    public void Method1Async(string param);  
    public void Method1Async(string param, object userState);  
    public event Method1CompletedEventHandler Method1Completed;  
  
    public void Method2Async(double param);  
    public void Method2Async(double param, object userState);  
    public event Method2CompletedEventHandler Method2Completed;  
  
    public void CancelAsync(object userState);  
  
    public bool IsBusy { get; }  
  
    // Class implementation not shown.  
}

这里虚构的 AsyncExample 类有两个方法,都支持同步和异步调用。 同步重载的行为类似于方法调用,它们对调用线程执行操作;如果操作很耗时,则调用的返回可能会有明显的延迟。 异步重载将在另一个线程上启动操作,然后立即返回,允许在调用线程继续执行的同时让操作“在后台”执行。

异步方法重载

异步操作可以有两个重载:单调用和多调用。 你可以通过方法签名来区分这两种形式:多调用形式有一个额外的参数,即 userState。 使用这种形式,你的代码可以多次调用 Method1Async(string param, object userState),而不必等待任何挂起的异步操作的完成。 另一方面,如果你尝试在前一个调用尚未完成时调用 Method1Async(string param),该方法将引发 InvalidOperationException

多调用重载的 userState 参数可帮助你区分各个异步操作。 应分别为各个 Method1Async(string param, object userState) 调用提供唯一值(例如,GUID 或哈希代码);这样,当各个操作完成时,事件处理程序便可以确定是哪个操作实例抛出了完成事件。

跟踪挂起的操作

如果你使用多调用重载,你的代码将需要跟踪挂起任务的 userState 对象(任务 ID)。 对于各个 Method1Async(string param, object userState) 调用,通常会生成新的唯一 userState 对象,并将它添加到集合中。 当对应于此 userState 对象的任务引发完成事件时,你的完成方法实现将检查 AsyncCompletedEventArgs.UserState 并将此对象从集合中删除。 在以这种方式使用时,userState 参数充当任务 ID 的角色。

在为你对多调用重载的调用中的 userState 提供唯一值时,一定要小心。 如果任务 ID 不唯一,将导致异步类引发 ArgumentException

取消挂起的操作

我们必须能够在异步操作完成之前随时取消它们,这一点很重要。 实现基于事件的异步模式的类包含 CancelAsync 方法(如果只有一个异步方法),或 MethodNameAsyncCancel 方法(如果有多个异步方法)。

允许多个调用采用 userState 参数的方法,此类方法可用于跟踪每个任务的生存期。 CancelAsync 采用 userState 参数,该参数可用于取消特定挂起任务。

扫描二维码关注公众号,回复: 11379630 查看本文章

一次只支持一个挂起操作的方法(如 Method1Async(string param))是不可取消的。

接收进度更新和增量结果

符合基于事件的异步模式的类可以为跟踪进度和增量结果提供事件。 此事件通常命名为 ProgressChangedMethodNameProgressChanged,其对应的事件处理程序将使用 ProgressChangedEventArgs 参数。

ProgressChanged 事件的事件处理程序可以检查 ProgressChangedEventArgs.ProgressPercentage 属性,以确定异步任务完成百分比。 此属性的范围是 0 到 100,可用来更新 ProgressBar.Value 属性。 如果有多个异步操作挂起,你可以使用 ProgressChangedEventArgs.UserState 属性来分辨出哪个操作在报告进度。

实现基于事件的异步模式的最佳做法

若要实现基于事件的异步模式,你必须提供一些保证来确保类的行为正确且类的客户端能够依赖这种行为。

完成

操作成功完成、出错或取消时,始终应调用 MethodNameCompleted 事件处理程序 。 任何情况下,应用程序都不应遇到这样的情况:应用程序保持空闲状态,而操作却一直不能完成。 此规则的例外情况是:异步操作本身设计为永不完成。

已完成的事件和EventArgs

针对每个单独的 MethodNameAsync 方法,请应用以下设计要求:

  • 在与该方法相同的类上定义 MethodNameCompleted 事件。
  • MethodNameCompleted 事件定义一个AsyncCompletedEventArgs,默认类名应采用 MethodNameCompletedEventArgs 形式。
  • 确保 EventArgs 类特定于 MethodName 方法的返回值。 在使用 EventArgs 类时,切勿要求开发人员强制转换结果。
  • 不要为返回void的方法定义EventArgs,而应使用 AsyncCompletedEventArgs 类的实例。
  • 应务必始终抛出 MethodNameCompleted 事件。 成功完成、出错或者取消时应引发此事件。 任何情况下,应用程序都不应遇到这样的情况:应用程序保持空闲状态,而操作却一直不能完成。
  • 确保可以捕获异步操作中发生的任何异常并将捕获的异常分配给 Error 属性。
  • 如果完成任务时出现错误,其结果应当不可访问。 当 Error 属性不为 null 时,确保访问 EventArgs 结构中的任何属性都会引发异常。 使用 RaiseExceptionIfNecessary 方法来执行此验证。
  • 如果发生任何异常,应抛出 MethodNameCompleted 事件,并将该异常分配给 AsyncCompletedEventArgs.Error 属性。
  • 如果类支持多个并发调用,应确保 MethodNameCompleted 事件包含相应的 userSuppliedState 对象。
  • 应确保在应用生命周期中适时对相应线程抛出 MethodNameCompleted 事件。

线程处理和上下文

为了使类正确运行,应当使用给定应用程序模型(包括 ASP.NET 和 Windows 窗体应用程序)的适当线程或上下文调用客户端事件处理程序,这一点很重要。 我们提供了两个重要的帮助器类,以确保你的异步类在任何应用程序模型中都能正确运行,这两个帮助器类是 AsyncOperationAsyncOperationManager

AsyncOperationManager 提供了 CreateOperation 方法,该方法会返回一个 AsyncOperationMethodNameAsync 方法调用 CreateOperation,且类使用返回的 AsyncOperation 跟踪异步任务的生存期。

若要将进程、增量结果和完成情况报告给客户端,请调用 AsyncOperation 上的 PostOperationCompleted 方法。 AsyncOperation 负责将对客户端事件处理程序的调用封送到合适的线程或上下文。

猜你喜欢

转载自www.cnblogs.com/yu_xing/p/event-based_asynchronous_pattern.html