async/await 不提速,而是释放线程以处理I/O密集型操作;必须返回Task或Task<T>;禁用async void(除UI事件);真异步需底层API支持,CPU密集型应选Task.Run。

直接说结论:async 和 await 不是“让方法变快”的魔法,而是让线程不卡住、能继续干别的事——尤其适合 I/O 密集型操作(比如 HTTP 请求、文件读写、数据库查询)。同步方法阻塞线程,异步方法释放线程,仅此而已。
async 方法必须返回 Task 或 Task<T>
这是编译器强制的规则,不是建议。你不能给一个 void 方法加 async 修饰符(除非是事件处理器),否则无法被正确 await,也难以捕获异常。
-
async Task:用于没有返回值的异步操作,比如SaveToFileAsync() -
async Task<int>:用于有返回值的异步操作,比如FetchUserIdAsync() - 别写
async void(除 UI 事件外)——异常会直接炸到线程池,无法用try/catch捕获 - 返回
Task.FromResult(42)是同步包装,不是真异步;真异步要靠底层 API 支持(如HttpClient.GetAsync、FileStream.ReadAsync)
await 只能用在 async 方法里,且会挂起当前方法直到 Task 完成
await 不是“开新线程”,它只是告诉编译器:“这儿先停一下,等这个 Task 完了再往下走,期间线程可以去干别的”。如果 Task 已完成(比如缓存命中),await 几乎不耗时。
- 不能在
catch、finally、using声明中直接await(C# 8+ 允许await using,但需类型实现IAsyncDisposable) -
await后续代码可能在不同线程执行(取决于SynchronizationContext,比如 WinForms/WPF 会自动切回 UI 线程,控制台程序则不一定) - 避免
Task.Wait()或Task.Result—— 这会同步阻塞,可能引发死锁(尤其在有上下文的 UI/ASP.NET 环境)
常见错误:把同步方法强行套上 async/await
比如这样写:
async Task<string> GetLocalDataAsync()
{
Thread.Sleep(1000); // 同步阻塞!
return "done";
}这完全没意义:async + Thread.Sleep 不但没释放线程,还多了一层状态机开销。真要模拟延迟,用 await Task.Delay(1000)。
- 真正异步的方法,内部调用的是操作系统级异步 I/O(如
ReadFileEx、WSARecv),不占线程 - 自己写的 CPU 密集型逻辑(如复杂计算、图像处理),该用
Task.Run拉到线程池跑,而不是套async/await - 混合使用时注意:不要在
Task.Run里又await一个 I/O 异步方法——这属于“双跳”,浪费线程池资源
ConfigureAwait(false) 是什么?什么时候加?
默认情况下,await 完成后会尝试回到原来的上下文(比如 UI 线程或 ASP.NET 请求上下文)。如果你的异步方法**不依赖上下文**(比如纯数据处理、后台服务逻辑),加 .ConfigureAwait(false) 可避免上下文切换开销,也防止潜在死锁。
- 库作者几乎都应该加:比如
return await httpClient.GetAsync(url).ConfigureAwait(false); - UI 层(如 WPF/WinForms 事件)通常不加,因为后续要更新控件,必须在 UI 线程
- ASP.NET Core 默认无
SynchronizationContext,所以ConfigureAwait(false)影响不大,但加上更明确 - 从 C# 12 开始,可在方法签名加
[SuppressMessage("Performance", "CA2016:UseConfigureAwait")]或全局配置,但手动加仍是主流做法
最容易被忽略的一点:异步不是银弹。一个没 IO、没等待、纯内存计算的方法,套 async/await 只会让性能更差——状态机、堆分配、上下文捕获全来了。判断是否该异步,唯一标准是:这个操作会不会让线程空等系统资源(磁盘、网卡、数据库连接)?如果是,就用;如果不是,别硬套。



















