找回密码
 立即注册
首页 业界区 业界 .NET 进阶之路:异步、并发与内存管理的系统性认知 ...

.NET 进阶之路:异步、并发与内存管理的系统性认知

吮槌圯 昨天 20:34
异步编程模式的演进与 TAP 最佳实践

.NET 的异步编程经历了三个时代。理解这段历史不是为了考古,而是因为你在维护老代码时必然会遭遇它们,理解它们才能优雅地迁移。
模式时代标志状态APM(异步编程模型).NET 1.xBeginXxx / EndXxx已淘汰EAP(基于事件的异步).NET 2.0XxxAsync + XxxCompleted 事件遗留代码TAP(基于任务的异步).NET 4.0+Task / async / await推荐使用TAP 方法的命名与签名规范

很多人写异步方法时忽视规范,导致 API 设计混乱。TAP 有一套严格的约定:
  1. // ✅ 标准命名:方法名 + Async 后缀
  2. public Task<int> ReadAsync(byte[] buffer, int offset, int count);
  3. // ✅ 已有同名 EAP 方法时,用 TaskAsync 后缀
  4. public Task<string> GetTaskAsync(string url);
  5. // ✅ 返回 void 的同步对应版本 → 返回 Task
  6. public Task SaveAsync(string path);
  7. // ✅ 返回 T 的同步对应版本 → 返回 Task<T>
  8. public Task<UserDto> GetUserAsync(int userId);
  9. // ❌ 避免:out/ref 参数在 TAP 中禁止使用
  10. // 应将多返回值包装为 tuple 或自定义类型
  11. public Task<(bool Success, string Error)> TryParseAsync(string input);
复制代码
Task 的生命周期:一个经常被忽视的细节

Task 有 冷任务(Cold Task)热任务(Hot Task) 之分。new Task(...) 创建的是冷任务,需要手动调用 Start()。但 TAP 方法返回的 Task 必须是已激活的热任务——调用者不应该也不需要调用 Start()。
⚠️ 常见错误
如果你在 TAP 方法内部通过 new Task() 构造任务后忘记调用 Start() 就返回它,调用者会陷入永久等待。始终确保返回的 Task 已处于运行状态。
异常处理的正确姿势

异步方法中的异常处理有一个重要原则:参数验证异常应该在 async 方法外层同步抛出,这样调用者能立即捕获,而不必 await 后才能发现错误。
  1. // ✅ 推荐:参数验证在外层同步完成
  2. public Task<int> ProcessAsync(string input)
  3. {
  4.     if (input == null)
  5.         throw new ArgumentNullException(nameof(input)); // 同步抛出
  6.     return ProcessCoreAsync(input); // 委托给真正的 async 方法
  7. }
  8. private async Task<int> ProcessCoreAsync(string input)
  9. {
  10.     // 真正的异步工作
  11.     var result = await DoWorkAsync(input);
  12.     return result;
  13. }
复制代码
取消令牌与进度报告:让异步操作可控

写了 3 年 .NET,你可能已经在用 CancellationToken,但真正理解它的状态机和设计模式的人并不多。
CancellationToken 的三种终态
  1.           Task 状态机:
  2. Created ──Start()──▶ Running
  3. ┌─────────────┼─────────────┐
  4. ▼             ▼             ▼
  5. Canceled Faulted RanToCompletion
  6. (取消请求) (未处理异常) (正常完成)
  7. │             │             │
  8. └─────────────┴─────────────┘
  9.        IsCompleted = true
复制代码
取消时 Task 进入 Canceled 状态,IsCompleted 返回 true,但 await 它会抛出 OperationCanceledException。
最佳实践:在计算密集型任务中轮询取消
  1. internal Task<Bitmap> RenderAsync(
  2.     ImageData data, CancellationToken cancellationToken)
  3. {
  4.     return Task.Run(() =>
  5.     {
  6.         var bmp = new Bitmap(data.Width, data.Height);
  7.         for (int y = 0; y < data.Height; y++)
  8.         {
  9.             // 每行检查一次取消请求,不要每像素都检查(性能损耗)
  10.             cancellationToken.ThrowIfCancellationRequested();
  11.             for (int x = 0; x < data.Width; x++)
  12.             {
  13.                 // 渲染像素 [x, y]
  14.             }
  15.         }
  16.         return bmp;
  17.     }, cancellationToken); // 传入 token 以便 Task 启动前就取消
  18. }
复制代码
进度报告:IProgress 的正确用法

不要用事件或回调来报告进度——IProgress 是官方推荐的模式,它能自动处理线程同步问题(回调总是在创建 Progress 实例的同步上下文中执行,通常是 UI 线程)。
  1. // 定义时接受 IProgress<T> 参数
  2. public async Task<string[]> FindFilesAsync(
  3.     string pattern,
  4.     CancellationToken ct = default,
  5.     IProgress<int> progress = null) // 允许为 null
  6. {
  7.     var results = new List<string>();
  8.     int count = 0;
  9.     await foreach (var file in EnumerateFilesAsync(pattern, ct))
  10.     {
  11.         results.Add(file);
  12.         progress?.Report(++count); // null 安全调用
  13.     }
  14.     return results.ToArray();
  15. }
  16. // 调用端:Progress<T> 捕获 UI 线程的同步上下文
  17. var reporter = new Progress<int>(count =>
  18.     progressBar.Value = count); // 这里可以安全更新 UI
  19. await FindFilesAsync("*.cs", ct, reporter);
复制代码

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册