APM 是 .NET 早期基于 IAsyncResult 的异步模式,现已被淘汰,新项目完全不该使用;EAP 主要用于旧 UI 框架,已标记过时;TAP 是当前唯一推荐的标准异步模式,全面支持 async/await、取消、异常和组合操作。
APM(Asynchronous Programming Model)是 .NET 早期的异步模式,基于 IAsyncResult 接口,靠 BeginXXX/EndXXX 成对方法实现。比如 FileStream.BeginRead 和 FileStream.EndRead。
它的问题很实际:回调嵌套深、异常处理分散、取消逻辑难统一、资源释放容易出错。.NET Core 2.0+ 已不再为新 API 添加 APM 支持,新项目完全不该选用 APM。
async/await 语法,无法自然融入现代 C# 异步流EndXXX 必须调用,否则可能丢失异常或阻塞资源(如未调用 EndRead 可能导致句柄泄漏)CancellationToken 并在回调中检查EAP(Event-based Asynchronous Pattern)以 MethodNameAsync + MethodNameCompleted 事件形式存在,典型如 WebClient.DownloadStringAsync 和 DownloadStringCompleted 事件。
它主要服务于 UI 框架的线程模型,自动把完成回调封送到 UI 线程(通过 SynchronizationContext),所以老式 WinForms/WPF 代码里还能看到。但它的设计本质是“为 UI 而妥协”,不是通用异步抽象。
Task,无法用 await,也不能参与 Task.WhenAll 等组合操作AsyncCompletedEventArgs 的 Error 和 Result 属性里,类型擦除严重(Result 是 object)[Obsolete],比如 SmtpClient.SendAsync
TAP(Task-based Asynchronous Pattern)是当前 C# 异步的标准,所有新 API 都遵循它:方法名以 Async 结尾,返回 Task 或 Task,支持 await,原生集成取消、进度报告和同步上下文。
它不是“另一种选择”,而是事实标准。哪怕你封装一个旧 APM 方法,也该用 TaskFactory.FromAsync 转成 TAP;调用 EAP 方法,也该用 TaskCompletionSource 包一层。
async 方法体内可直接 await 任何 TAP 方法,包括你自己写的 async Task GetDataAsync()
CancellationToken 控制取消,且绝大多数 TAP
方法都提供带 token 的重载(如 HttpClient.GetAsync(uri, token))AggregateException),调试体验好public async TaskFetchDataAsync(string url, CancellationToken ct = default) { using var client = new HttpClient(); var response = await client.GetAsync(url, ct); // 自动响应取消 return await response.Content.ReadAsStringAsync(ct); }
真实项目常要对接老代码或第三方库,可能同时遇到三种模式。这时最危险的操作是“假装它们等价”——比如把 BeginInvoke 回调里直接调 EndInvoke,却不考虑同步上下文;或给 EAP 事件 handler 加 async void 导致异常无法捕获。
Completed 事件处理器里写 async void —— 改用 async Task 并手动调用 await 后续逻辑,否则异常会直接崩掉进程Task.Factory.FromAsync 包装 APM 时,必须确保 EndXXX 被调用 —— 它内部已帮你做了,但自定义包装时容易漏task.Result 或 task.Wait())极易引发死锁,尤其在有 SynchronizationContext 的环境(WinForms/WPF/ASP.NET MVC)异步模型的差异不在语法糖,而在控制流所有权和错误传播路径。选错起点,后面每一步都在补洞。