async/await 本质是异步i/o而非多线程,不应滥用task.run包装同步方法;正确做法是直接调用原生异步api(如getasync、readasync),避免async void,合理处理task.whenall异常。

async/await 不是多线程,别用 Task.Run 包同步方法充数
很多人一写 async 就下意识套 Task.Run(() => SomeSyncMethod()),以为这样就“异步”了。其实这只是把同步操作扔到线程池里跑,既没节省资源,还可能引发上下文切换开销和死锁风险(尤其在 UI 或 ASP.NET 同步上下文里)。
真正该走异步路径的,是本身支持 async 的 I/O 操作:文件读写、HTTP 请求、数据库查询等。它们底层调用操作系统异步 I/O(如 Windows 的 IOCP),不占线程。
- ✅ 正确做法:直接用
HttpClient.GetAsync()、FileStream.ReadAsync()、DbCommand.ExecuteReaderAsync() - ❌ 错误信号:看到
Task.Run+ 长耗时 CPU 方法(比如解析大 JSON、加密计算),说明你本该用后台线程,但不该伪装成async接口 - ⚠️ 注意:ASP.NET Core 默认禁用同步上下文,所以
ConfigureAwait(false)在大多数 Web 场景已非必需;但在 WinForms/WPF 里仍建议加,避免跨线程访问控件异常
await 后续代码不一定回到原线程,但状态机保证局部变量不丢
await 暂停方法执行后,恢复时是否回到原始线程,取决于当前 SynchronizationContext 和 TaskScheduler。UI 线程会自动回切,ASP.NET 旧版(.NET Framework)会尝试,而 .NET Core+Web 默认不回。
但你不需要操心线程归属——C# 编译器生成的状态机早已把局部变量(包括 for 循环的 i、临时 string、using 资源)捕获进字段,确保 await 前后能接着用。
- 常见错觉:“await 后变量丢了”,其实是忘了
async方法返回的是Task,没await或没.Result(后者会阻塞!)导致逻辑没执行 - 调试时注意:断点打在
await后行,第一次命中是“恢复执行”,不是“刚进来”;看调用栈里有没有MoveNext可确认是否在状态机中 - 性能提示:过度拆分细粒度
await(比如每行都 await 一个 trivial Task)会增加状态机开销,不如合并操作
不要在构造函数、属性 getter、事件处理函数里直接写 async void
async void 是唯一不能被等待、无法传播异常、且异常会直接炸到 AppDomain.UnhandledException 的签名。它只该用于真正的顶层事件入口,比如 WinForms 的 button_Click。
构造函数不能是 async(语法不允许),属性也不该触发异步加载(违反封装与可预测性)。如果真要延迟初始化,用 Lazy<task>></task> 或显式暴露 InitializeAsync() 方法。
- ❌ 危险写法:
public string Name => await GetNameAsync();(编译不过)或private async void Load() { ... }被私有调用 - ✅ 替代方案:
public async Task<string> GetNameAsync()</string>+ 外部调用方负责await;或用Task.Run包装并返回Task(仅限必须启动后台任务时) - ⚠️ ASP.NET MVC/WebAPI 中,Controller Action 必须返回
Task<iactionresult></iactionresult>或IActionResult,混用async void会导致请求提前结束、响应为空
Task.WhenAll 和 Task.WhenAny 的错误处理容易漏掉 AggregateException
Task.WhenAll 返回一个 Task,但它内部任意子任务抛异常,都会包装成 AggregateException。直接 try/catch 会抓不到原始异常类型,且 .InnerExceptions 可能为空(当只有一个异常时,.NET 6+ 会扁平化)。
更糟的是,WhenAll 遇到失败默认“短路”——但实际它会等所有任务完成才抛出异常(区别于 WhenAny 的“谁先完成谁先返回”)。
- ✅ 安全写法:
var results = await Task.WhenAll(tasks);然后单独检查每个task.IsFaulted,或用await task逐个 await(适合需精确控制错误位置的场景) - ✅ 捕获原始异常:
catch (Exception ex) when (!(ex is OperationCanceledException)),比 catchAggregateException更可靠 - ⚠️ 注意:
Task.WhenAll不等于“并发安全”,它不解决共享变量竞态;若多个任务改同一个List<t></t>,仍需锁或ConcurrentBag
事情说清了就结束。最常被忽略的,是把 async 当作性能银弹——它只优化 I/O 等待,不加速 CPU 计算;而最危险的,是让异常从 async void 里漏出去,连日志都抓不到。











