async/await 解决的是I/O等待时不阻塞线程的问题,核心是利用系统级异步I/O而非多线程;必须返回Task或Task<T>,避免async void(仅限UI事件);await不创建新线程,不可混用.Result/.Wait()以防死锁;后台逻辑应普遍使用ConfigureAwait(false)减少上下文切换开销。

async/await 不是多线程开关,也不是性能银弹;它解决的核心问题是「不让线程干等 I/O」——比如发 HTTP 请求、读文件、查数据库时,线程不必卡住,可以去处理别的事。
async 方法必须返回 Task 或 Task<T>,不能只写 void
除了事件处理器(如 Button_Click),其他地方写 async void 是危险信号。它会让异常逃逸、无法 await、难以测试。
-
async Task:无返回值,可被await,推荐用于所有普通方法 -
async Task<string>:有返回值,调用方可用await获取结果 -
async void:仅限 UI 事件(如 WPF/WinForms 的按钮点击),且异常会直接崩掉应用
错误示例:async void SaveData() → 调用方无法知道它何时完成,也无法捕获内部抛出的 HttpRequestException。
await 不等于“开新线程”,它只是挂起当前方法,不阻塞线程
await 后面的对象必须是「可等待的」(即实现 GetAwaiter() 的类型),最常见的是 Task 和 Task<T>。它不会强制分配新线程,而是利用 I/O 完成端口(Windows)或 epoll/kqueue(Linux/macOS)让系统通知完成时机。
- HTTP 请求走
HttpClient.GetStringAsync()→ 底层用 OS 级异步 I/O,不占线程 -
Task.Run(() => HeavyCalc())→ 这才真正把 CPU 工作扔进线程池,属于多线程场景,和 await 本身无关 - 误用:
await Task.Delay(1000).ConfigureAwait(false)在 UI 线程里调用没问题;但若写成await Task.Run(() => Thread.Sleep(1000)),就是用线程池白占资源
别在 async 方法里混同步阻塞调用,比如 .Result 或 .Wait()
这极易引发死锁,尤其在有同步上下文(SynchronizationContext)的环境,如 WinForms、WPF、ASP.NET(.NET Framework 版本)中。
- 错误写法:
var data = GetDataAsync().Result;→ 主线程等着 Task 完成,而 Task 又想回调回主线程,互相卡住 - 正确写法:一路 async/await 向上传递,让整个调用链都异步化
- 例外情况:控制台程序或 ASP.NET Core(无默认 SynchronizationContext)下
.Result不会死锁,但依然不推荐——它会阻塞线程,浪费资源
如果真要同步等待(极少见),用 GetAwaiter().GetResult() 更明确,但它和 .Result 一样会传播异常,且不解决阻塞问题。
ConfigureAwait(false) 不是可选项,而是默认该加的优化项
除非你明确需要回到原始上下文(比如更新 UI 控件),否则几乎所有后台逻辑都应该加 .ConfigureAwait(false)。它告诉编译器:任务完成后,不用非得调度回原来的线程。
- 没加时:
await DoWorkAsync()→ 完成后强制切回 UI 线程或 ASP.NET 请求上下文,带来调度开销 - 加了后:
await DoWorkAsync().ConfigureAwait(false)→ 完成后在任意线程池线程继续执行,减少上下文切换 - 漏加的典型后果:高并发 Web API 中,大量 await 回调争抢同一个同步上下文,成为性能瓶颈
最容易被忽略的一点是:这个配置必须加在每一个 await 表达式末尾,而不是只在方法开头设一次——因为每个 await 都是独立的挂起点。



















