async/await 是编译器生成状态机以简化异步I/O的同步风格写法,非多线程;必须返回Task/Task<T>,避免async void(仅限事件处理器);await对象需支持GetAwaiter();库中每个await都应配ConfigureAwait(false)。

async/await 不是多线程,也不是“让代码变快”的银弹;它本质是编译器帮你重写状态机,用来简化异步 I/O 操作的线性表达——用同步风格写异步逻辑,但阻塞的是逻辑流,不是线程。
async 方法必须返回 Task 或 Task<T>,void 只能用于事件处理器
这是最常踩的坑:把普通方法加个 async 就以为能 await,结果编译报错或行为异常。
-
async void无法被等待、无法捕获异常(异常会直接抛到 SynchronizationContext,可能崩掉 UI 线程) - 后台任务、命令行入口、Web API 控制器方法,一律用
async Task或async Task<T> - WinForms/WPF 的按钮点击事件可以是
async void,仅因事件签名强制要求;但内部仍应尽快转为await一个Task,别在void方法里写复杂异步链
await 后面必须是可等待对象(awaiter),不是所有返回 Task 的方法都适合直接 await
比如你写了 var t = SomeAsyncMethod(),然后想在后面某个分支才 await t——这没问题;但如果你误把 Task.Run(() => {...}) 当作“启动后台线程”来用,就容易混淆用途。
-
Task.Run是 CPU 密集型操作的包装,会抢占线程池线程;I/O 操作(如HttpClient.GetAsync、FileStream.ReadAsync)本身不占线程,用Task.Run包一层反而降低性能 -
await的对象必须有GetAwaiter()方法且返回实现了INotifyCompletion的类型;自定义类型要实现GetAwaiter才能被await - 不要
await Task.Delay(1000).ConfigureAwait(false)这样链式调用——ConfigureAwait是对Task实例调用的,不是对await表达式本身
ConfigureAwait(false) 不是可选项,而是库作者的必选项
在类库(.NET Standard / .NET Core 类库)中,不加 ConfigureAwait(false) 可能导致死锁或上下文切换开销——尤其当调用方是 UI 线程或 ASP.NET 同步上下文时。
- ASP.NET Core 默认无
SynchronizationContext,所以影响小;但 ASP.NET Framework(.NET 4.x)和 WinForms/WPF 有,不配置就可能卡死 - 规则很简单:只要你的方法不依赖
SynchronizationContext(比如不更新 UI 控件、不访问HttpContext.Current),就在每个await后加.ConfigureAwait(false) - 不是“整个方法开头配一次”,而是每个
await表达式都要配:await http.GetAsync(url).ConfigureAwait(false)
真正难的不是语法,是判断哪段该异步、哪段不该——比如数据库查询用 ExecuteReaderAsync,但后续的 List<T>.Find 或 JSON 序列化是纯内存操作,不该套 Task.Run。async/await 的边界,永远由 I/O 和 CPU 划分,不是由“看起来慢”决定。


















