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

基于事件的异步模式提供了一种在类中使用熟悉的事件和委托语义公开异步行为的有效方法。 若要实现基于事件的异步模式,需要遵循一些特定的行为要求。 以下部分介绍在实现遵循基于事件的异步模式的类时应考虑的要求和准则。

有关概述,请参阅 实现基于事件的异步模式

必需的行为保证

如果实现基于事件的异步模式,则必须提供许多保证,以确保类的行为正常,并且类的客户端可以依赖于此类行为。

补全

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

已完成的事件和 EventArgs

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

  • 在方法所在的同一类上定义 MethodNameCompleted 事件。

  • 为派生自 EventArgs 类的 MethodNameCompleted 事件定义一个 类和随附委托AsyncCompletedEventArgs。 默认类名应为 MethodNameCompletedEventArgs 格式。

  • 确保类 EventArgs 特定于 MethodName 方法的返回值。 在使用 EventArgs 类时,切勿要求开发人员强制转换结果。

    下面的代码示例分别显示此设计要求的良好和错误的实现。

// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e)
{
    DemoType result = e.Result;
}

// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)
{
    DemoType result = (DemoType)(e.Result);
}
  • 不要为返回EventArgs的返回方法定义void类。 改为使用 AsyncCompletedEventArgs 类的实例。

  • 应务必始终抛出 MethodNameCompleted 事件。 应在成功完成操作时、出错时或取消时引发此事件。 任何情况下,应用程序都不应遇到这样的情况:应用程序保持空闲状态,而操作却一直不能完成。

  • 确保捕获异步作中发生的任何异常,并将捕获的 Error 异常分配给该属性。

  • 如果任务完成时出现错误,则不应允许访问结果。 当Error属性不是null时,确保访问EventArgs结构体中的任何属性会引发异常。 RaiseExceptionIfNecessary使用该方法执行此验证。

  • 将超时建模为错误。 发生超时时,引发 MethodNameCompleted 事件并向其分配属性TimeoutExceptionError

  • 如果类支持多个并发调用,请确保 MethodNameCompleted 事件包含相应的 userSuppliedState 对象。

  • 确保 MethodNameCompleted 事件是在适当的线程上引发的,并在应用程序生命周期中的适当时间引发。 有关详细信息,请参阅“线程和上下文”部分。

同时执行操作

  • 如果类支持多个并发调用,应让开发人员可以单独跟踪各个调用,具体操作是定义 MethodNameAsync 重载,此重载需要使用对象赋值状态参数或任务 ID(名为 userSuppliedState)。 此参数应始终是 MethodNameAsync 方法签名中的最后一个参数。

  • 如果类定义了需要使用对象赋值状态参数或任务 ID 的 MethodNameAsync 重载,应务必使用相应任务 ID 跟踪操作的生存期,并将它 返回给完成事件处理器。 有一些用来提供帮助的帮助器类。 有关并发管理的详细信息,请参阅 如何:实现支持基于事件的异步模式的组件

  • 如果类定义了不带状态参数的 MethodNameAsync 方法,并且不支持多个并发调用,请确保在前面的 MethodNameAsync 调用完成之前调用 MethodNameAsync 的任何尝试都引发一个InvalidOperationException

  • 一般情况下,不要引发异常,即使多次调用不带参数的MethodNameuserSuppliedState方法导致有多个待处理的操作。 如果类无法显式处理这种情况,将引发异常,但可假定开发人员能够处理多个不可区分回调。

访问结果

进度报告

  • 支持进度报告(如果可能)。 这使开发人员能够在使用你的类时提供更好的应用程序用户体验。

  • 如果实现 ProgressChangedMethodNameProgressChanged 事件,请确保在该操作的 MethodNameCompleted 事件引发之后,不会为特定异步操作引发此类事件。

  • 如果填充了标准 ProgressChangedEventArgs ,请确保 ProgressPercentage 始终可以被解释为百分比。 百分比不需要准确,但它应表示百分比。 如果进度报告指标必须是百分比以外的内容,请从 ProgressChangedEventArgs 类派生类并保留 ProgressPercentage 为 0。 避免使用非百分比的报告指标。

  • 确保该事件在适当的线程上并在应用程序生命周期中的适当时间被引发。 有关详细信息,请参阅“线程和上下文”部分。

IsBusy 实现

  • 如果类支持多个并发调用,请不要公开 IsBusy 属性。 例如,XML Web 服务代理不公开属性 IsBusy ,因为它们支持多个异步方法的并发调用。

  • 属性IsBusy应在调用 trueAsync 方法之后以及引发 MethodNameCompleted 事件之前返回。 否则,它应返回 false。 这些BackgroundWorkerWebClient组件是公开IsBusy属性的类的示例。

取消

  • 支持取消(如果可能)。 这使开发人员能够在使用你的类时提供更好的应用程序用户体验。

  • 在取消的情况下,在 Cancelled 对象中设置 AsyncCompletedEventArgs 标志。

  • 确保访问结果的任何尝试都将引发 InvalidOperationException,指出该操作已被取消。 AsyncCompletedEventArgs.RaiseExceptionIfNecessary使用该方法执行此验证。

  • 确保对取消方法的调用始终能够成功返回结果,并且绝不会引发异常。 通常,客户端不会被通知关于一个操作在任何给定时间是否真正可取消,也不会被通知之前发出的取消请求是否成功。 不过,应用程序在取消成功时总能得到通知,因为应用程序参与了完成状态。

  • 当操作被取消时引发 MethodNameCompleted 事件。

错误和异常

线程处理和上下文

对于类的正确操作,至关重要的是客户端的事件处理程序必须在给定应用程序模型中,包括 ASP.NET 和 Windows 窗体应用程序的正确线程或上下文上被调用。 提供了两个重要的帮助程序类,以确保异步类在任何应用程序模型下的行为正确: AsyncOperationAsyncOperationManager

AsyncOperationManager 提供一种方法, CreateOperation该方法返回一个 AsyncOperationMethodNameAsync 方法调用CreateOperation,并且类使用返回的AsyncOperation来跟踪异步任务的生命周期。

若要向客户端报告进度、增量结果和完成情况,请在Post上调用OperationCompletedAsyncOperation方法。 AsyncOperation 负责将客户端事件处理程序的调用传递到正确的线程或上下文。

注释

如果显式想要违反应用程序模型的策略,则可以规避这些规则,但仍受益于使用基于事件的异步模式的其他优势。 例如,你可能希望 Windows 窗体中运行的类可以自由线程化。 只要开发人员了解隐含的限制,就可以创建一个自由线程类。 控制台应用程序不会同步 Post 调用的执行。 这会导致按错误的顺序引发 ProgressChanged 事件。 如果希望实现对Post调用的顺序执行,请实现并安装System.Threading.SynchronizationContext类。

有关使用AsyncOperationAsyncOperationManager来启用异步操作的详细信息,请参阅如何:实现支持基于事件的异步模式的组件

准则

  • 理想情况下,每个方法调用都应独立于其他方法。 应避免将调用与共享资源耦合。 如果要在调用之间共享资源,则需要在实现中提供适当的同步机制。

  • 不建议要求客户端实现同步的设计。 例如,可以有一个异步方法,该方法接收全局静态对象作为参数;此类方法的多个并发调用可能会导致数据损坏或死锁。

  • 如果你使用多个调用重载(签名中的 userState)实现方法,你的类将需要管理由一系列用户状态、任务 ID 及其相应的挂起操作构成的一个集合。 此集合应受 lock 区域保护,因为各种调用在集合中添加和删除 userState 对象。

  • 请考虑在可行且适当的情况下重用 CompletedEventArgs 类。 在这种情况下,命名与方法名称不一致,因为给定的委托和 EventArgs 类型并不是仅限于单个方法。 不过,强制开发人员对从 EventArgs 上的属性检索的值进行强制转换,是绝对不可取的。

  • 如果要创作派生自 Component的类,请不要实现并安装自己的 SynchronizationContext 类。 应用程序模型控制的是所使用的 SynchronizationContext,而不是组件。

  • 使用任何类型的多线程处理时,可能会暴露非常严重和复杂的漏洞。 在实现使用多线程的任何解决方案之前,请参阅 托管线程处理最佳做法

另请参阅