异步编程不能提升CPU密集型任务性能,仅优化I/O等待;ConfigureAwait(false)在类库中必须使用以防死锁;异常堆栈易失真需手动包装;async void仅限UI事件处理;跨框架兼容性细节需谨慎。
很多人误以为 async/await 能让计算变快,其实它只优化 I/O 等待时间。比如对一个大数组做排序、图像处理或加密解密,用 Task.Run(() => HeavyComputation()) 包裹后,只是把工作扔到线程池里执行,并不减少总耗时,还增加了调度开销和上下文切换成本。
async 的场景:HTTP 请求(HttpClient.GetAsync)、文件读写(File.ReadAllTextAsync)、数据库查询(DbCommand.ExecuteReaderAsync)——这些本质是等待操作系统完成 I/O,期间线程可被复用async 且不配合 Task.Run,编译器会警告“此 async 方法缺少 await”,运行时也仍是同步阻塞Task.Run 反而比直接同步执行更慢,因为线程池排队 + 状态机分配有额外开销在类库或底层工具方法中漏掉 ConfigureAwait(false),可能引发死锁或 UI 响应卡顿。它的作用是告诉运行时:await 完成后**不要强制回调回原始同步上下文**(比如 WinForms 的 UI 线程、ASP.NET Classic 的 HttpContext)。
SynchronizationContext,所以多数情况下不加也不会出问题;但 ASP.NET Framework 或 WPF/WinForms 项目里,如果在 UI 线程调用 GetAwaiter().GetResult() 或错误地用了 .Result,就极易死锁ConfigureAwait(false),否则使用者在非 UI 环境引用该库时,可能因意外捕获上下文导致性能下降甚至异常TextBox.Text,必须在 UI 线程执行异步方法抛出异常后,堆栈信息会包含状态机内部方法(如 MoveNext),原始调用点可能被掩盖。尤其在多层 await 链路中,InnerException 层级变深,调试时第一眼看不到出错的真实行号。
try/catch 捕获异常时,别只看 e.ToString(),要逐层检查 e.InnerException
await using 和更清晰的异常传播,但旧项目若还在用 .NET Framework 4.7.2,建议在关键异步入口处手动包装异常:try {
await DoSomethingAsync();
}
catch (Exception ex) {
throw new InvalidOperationException("调用 DoSomethingAsyn
c 失败", ex);
}Assert.ThrowsAsync() 而不是 Assert.Throws() ,否则会误判为未抛异常async void 方法无法被 await,异常会直接炸到 SynchronizationContext 或进程级,极难捕获。它唯一合理用途是 UI 事件处理器(如 Button_Click)。
async void;应该统一用 async Task
Task 是标准做法;写成 async void 会导致请求提前结束、日志缺失、监控失效async void 方法几乎不可能——没有返回值,没地方 await,只能靠超时或副作用判断,可靠性极低ValueTask 在 .NET Core 2.1+ 才稳定支持,老项目升级时若盲目替换 Task,可能引入隐式装箱或生命周期错误;还有 async 方法里用 lock 会编译失败,必须改用 SemaphoreSlim.WaitAsync。这些都不是理论问题,而是上线后才暴露的坑。