[异步编程]异步编程模式
.NET 支持三个异步编程模式:
-
异步编程模型 (APM)(旧版)
-
基于事件的异步模式 (EAP)(旧版)
-
基于任务的异步模式 (TAP)(建议用于新开发)
异步编程模型 (APM)
使用 IAsyncResult 设计模式的异步操作是通过名为 BeginOperationName
和 EndOperationName
的两个方法来实现的,这两个方法分别开始和结束异步操作 OperationName 。 例如, FileStream 类提供 BeginRead 和 EndRead 方法来从文件异步读取字节。 这两个方法实现了 Read 方法的异步版本。
在调用 BeginOperationName
后,应用程序可以继续在调用线程上执行指令,同时异步操作在另一个线程上执行。 每次调用 BeginOperationName
时,应用程序还应调用 EndOperationName
来获取操作的结果。
BeginOperationName
方法采用该方法的同步版本的签名中声明的任何参数(由值传递或由引用传递)。 BeginOperationName
方法签名中不包含任何输出参数。 BeginOperationName
方法签名另外还包括两个其他参数。 第一个参数定义一个 AsyncCallback 委托,此委托引用在异步操作完成时调用的方法。 如果调用方不希望在操作完成后调用方法,它可以指定 null
。 第二个参数是一个用户定义的对象。 此对象可用来向异步操作完成时调用的方法传递应用程序特定的状态信息。
如果调用 EndOperationName
时 IAsyncResult 对象表示的异步操作尚未完成,则 EndOperationName
将在异步操作完成之前阻止调用线程。
如果应用程序在接收到异步操作结果之前不能进行任何其他工作,则必须在获得这些结果之前先阻止该应用程序进行其他工作。 若要在异步操作完成之前阻止应用程序,可以使用下列方法之一:
- 从应用程序的主线程调用
EndOperationName
,阻止应用程序执行,直到操作完成之后再继续执行。 - 使用 AsyncWaitHandle 来阻止应用程序执行,直到一个或多个操作完成。result.AsyncWaitHandle.WaitOne();
在异步操作完成时不需要阻止的应用程序可使用下列方法之一:
-
按以下方式轮询操作完成状态:定期检查 IsCompleted 属性,操作完成后调用
EndOperationName
。 有关演示此方法的示例,请参阅 轮询异步操作的状态。 -
使用 AsyncCallback 委托来指定要在操作完成时调用的方法。 有关演示此方法的示例,请参阅 使用 AsyncCallback 委托结束异步操作。
基于事件的异步模式 (EAP)
那些同时执行多项任务、但仍能响应用户交互的应用程序通常需要实施一种使用多线程的设计方案。
对于相对简单的多线程应用程序,BackgroundWorker 组件提供了一个简单的解决方案。 对于更复杂的异步应用程序,请考虑实现一个符合基于事件的异步模式的类。
支持基于事件的异步模式的类将具有一个或多个命名为 MethodNameAsync 的方法。 这些方法可能会创建同步版本的镜像,这些同步版本会在当前线程上执行相同的操作。 该类还可能具有 MethodNameCompleted 事件,并且可能会具有 MethodNameAsyncCancel(或只是 CancelAsync)方法。PictureBox 是一个支持基于事件的异步模式的典型组件。
基于任务的异步模式 (TAP)
基于任务的异步模式 (TAP) 使用单个方法表示异步操作的开始和完成。
生成 TAP 方法
使用编译器
任何归于 async
关键字的方法都被视为异步方法。
手动生成 TAP 方法
如要自己实现 TAP,你需要创建一个 TaskCompletionSource<TResult> 对象、执行异步操作,并在操作完成时,调用 SetResult、SetException、SetCanceled 方法,或调用这些方法之一的Try
版本。
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state) { var tcs = new TaskCompletionSource<int>(); stream.BeginRead(buffer, offset, count, ar => { try { tcs.SetResult(stream.EndRead(ar)); } catch (Exception exc) { tcs.SetException(exc); } }, state); return tcs.Task; }
工作负载
如果是纯粹的计算密集型方法,应只公开为同步实现。 如果方法是 I/O 密集型,应只公开为异步实现。
计算密集型任务
System.Threading.Tasks.Task 类非常适合表示计算密集型操作。 默认情况下,它利用 ThreadPool 类中的特殊支持来提供有效的执行,还对执行异步计算的时间、地点和方式提供重要控制。
通过以下方式生成计算密集型任务:
- 在 .NET Framework 4 中,使用 TaskFactory.StartNew 方法,这种方法接受异步执行委托(通常是 Action<T> 或 Func<TResult>)。
- 在 .NET Framework 4.5 及更高版本(包括 .NET Core 和 .NET Standard)中,使用静态 Task.Run 方法作为 TaskFactory.StartNew 的快捷方式。
- 想要分别生成并计划任务时,请使用
Task
类型或Start
方法的构造函数。 - 使用 Task.ContinueWith 方法的重载。
- 使用 TaskFactory.ContinueWhenAll 和 TaskFactory.ContinueWhenAny 方法。
I/O 密集型任务
若要创建一个不应由线程直接支持其全部执行的任务,请使用 TaskCompletionSource<TResult> 类型。 此类型公开一个返回关联 Task 实例的 Task<TResult> 属性。 此任务的生命周期是由 TaskCompletionSource<TResult> 方法控制的,比如 SetResult、SetException、SetCanceled 以及它们的 TrySet
变形。
计算密集型和 I/O 密集型混合任务
异步方法不只局限于计算密集型或 I/O 密集型操作,还可以是两者的结合。