如何在遗留代码中使用 TAP 模式

.NET 异步编程模式

.NET Framework 提供三种异步编程模式:

  1. Asynchronous Programming Model(APM),又叫 IAsyncResult 模式

  2. Event-based Asynchronous Pattern(EAP)

  3. Task-based Asynchronous Pattern(TAP)

新代码推荐使用 TAP 模式,APM 和 EAP 只需在维护遗留代码(.NET Framework 4.5 之前的代码)时使用。

APM 指南

程序员有多种方法来访问异步操作的结果。具体选择那种方法取决于程序是否具有可在异步操作完成时执行的指令。如果程序在收到异步操作的结果之前无法执行任何其他工作,则程序必须阻塞,直到结果可用。

可以使用以下几种方法来阻塞程序:

  1. 在程序的主线程调用 End OperationName 方法,阻塞程序执行,直到异步操作完成

  2. 使用 AsyncWaitHandle 阻塞程序执行,等待一个或多个异步操作完成

  3. 无需阻塞程序,用下列方法之一来检测异步操作的完成:

    1. 轮询 IsCompleted 属性检查异步操作的状态,并在操作完成时调用 End OperationName 方法

    2. 使用 AsyncCallback 委托指定异步操作完成时要调用的方法。具体请参考使用 AsyncCallback 委托结束异步操作

EAP 指南

下面描述了实现 EAP 模式时应考虑的要求和指南。在实现 EAP 时,重要的是提供一些保证,以确保你的类正常工作,并且该类的消费类都可以依赖它的行为。

以下是 EAP 的最佳实践列表:

  1. 异步操作成功完成、出错或取消后,一定要调用 MethodNameCompleted 事件处理程序。异步操作不能有空闲状态,一定要保证能执行完毕。此规则唯一的例外就是异步操作本身就被设计为永远不会完成

  2. 对于每个 MethodNameAsync 方法,在方法所在类的内部定义 MethodNameCompleted 事件

  3. 为 MethodNameCompleted 事件定义 EventArgs 类和附带的委托。类名格式应为 MethodNameCompletedEventArgs

  4. 确保 EventArgs 与 MethodName 方法的返回值一一对应。使用 EventArgs 时,不应要求程序员再做转换

  5. 确保捕获异步操作中发生的任何异常,并将捕获的异常赋给 Error 属性

  6. 如果您的类支持多并发调用,请确保 MethodNameCompleted 事件中包含相应的 userSuppliedState 对象

  7. 确保在程序生命周期的适当的线程上的适当时刻引发 MethodNameCompleted 事件

  8. 如果在执行异步操作期间出错,则无法访问结果。确保在 Error 不为 null 时访问 AsyncCompletedEventArgs 中的任何属性会引发 Error 引用的异常。 AsyncCompletedEventArgs 类有为此提供的 RaiseExceptionIfNecessary 方法

关于 EAP 和 APM 的更多细节请参考 Deciding When to Implement the Event-based Asynchronous Pattern

重构遗留异步代码

怎样重构?

重构包含 APM 和 EAP 模式的遗留代码时,你有两种选择:

  1. 用 TAP 模式去重写

  2. 将遗留代码集成到 TAP 库中,然后用新代码调用它

不好说那种方法更好,但在具体选择那种方法之前,你可以参考以下几个注意事项。

哪个方案?

倾向于重写:

  1. 功能很简单。过去需要大量代码的一些操作现在很容易实现

  2. 程序需要跨平台。如果你需要在 Linux / Mac上运行,你可以考虑基于 .NET Core 进行重写

  3. 你或你的团队是代码的所有者。重写可以提升代码的可读性和可控性

倾向于集成:

  1. 你不是代码的所有者。例如你使用的是第三方库,或者与不共享代码的团队合作,你就无法重写

  2. 代码的功能很复杂。多年来已经写了很多异步代码,且它的代码量还在不断的膨胀,要重写它是个艰巨的任务

这篇文章 详细讲解了如何将老代码集成到 TAP 库。