async void仅用于UI事件处理器,业务逻辑必须用async Task;ConfigureAwait(false)在无同步上下文场景必加;勿用Task.Run包装I/O异步方法;禁用.Result/.Wait()阻塞调用。
async void 里写业务逻辑这是最常踩的坑:只要不是事件处理函数(比如按钮点击),就绝不要用 async void。它无法被 await,异常会直接炸到同步上下文,导致应用崩溃且难以捕获。
async void 方法一旦抛出未处理异常,会触发 AppDomain.UnhandledException 或 TaskScheduler.UnobservedTaskException,调试时根本看不到堆栈归属Task 或 Task,哪怕不 await 也得用 async Task
async void 的场景:WPF/WinForms/UWP 的事件处理器,例如 private async void Button_Click(...)
Conf
igureAwait(false) 不是可有可无的配置项在类库、底层工具方法、ASP.NET Core 中间件等**不依赖同步上下文**的场景下,漏掉 ConfigureAwait(false) 可能引发死锁或性能下降——尤其在 UI 线程或旧版 ASP.NET(非 Core)中。
await 会尝试捕获当前 SynchronizationContext 并回调回去;服务端代码不需要这个行为,反而造成线程争抢SynchronizationContext,但为兼容性与明确意图,仍建议显式写 .ConfigureAwait(false)
ConfigureAwait(false),否则 Dispatcher.Invoke 类操作会失败Task.Run 包裹已异步的 I/O 操作把 HttpClient.GetStringAsync() 或 FileStream.ReadAsync() 再套一层 Task.Run(() => ...),等于用线程池线程去等 I/O 完成,纯属浪费资源。
Task.Run 却强行调度到线程池,增加调度开销和上下文切换Task.Run 脱离当前上下文await Task.Run(() => httpClient.GetStringAsync(url)); // ❌正确写法:
await httpClient.GetStringAsync(url); // ✅
async 方法里的同步阻塞调用在 async 方法中调用 .Result、.Wait() 或 GetAwaiter().GetResult(),极易引发死锁,尤其在有同步上下文的环境(如 WinForms 主线程、旧版 ASP.NET)。
await task.ConfigureAwait(false) + GetAwaiter().GetResult()(仅限无上下文环境).Result,或者类库作者忘了加 ConfigureAwait(false)。异步不是加几个关键字就完事,关键在整条调用链是否真正“放手”。