async void 仅限事件处理器使用,业务方法必须返回 Task 或 Task<T>;await 线程归属取决于上下文,需谨慎处理 SynchronizationContext 和 HttpContext;构造函数不可异步;Task.Run 不替代原生 async I/O。

async void 是危险的,别用它做业务逻辑
除非你写的是事件处理器(比如按钮点击),否则 async void 会导致异常无法被上层捕获、无法 await、无法参与任务调度——它本质上不是“可等待的异步操作”,而是一个“火了就不管”的后台发射器。
常见错误现象:TaskCanceledException 或 NullReferenceException 直接崩掉主线程,调试器还抓不到堆栈;或者 UI 线程卡住,因为异常没被处理,SynchronizationContext 被破坏。
- 只在 WPF/WinForms 的事件处理方法中用
async void,例如private async void Button_Click(object sender, RoutedEventArgs e) - 所有业务方法必须返回
Task或Task<T>,哪怕不返回值也写成async Task DoSomethingAsync() - 如果你硬要“假装返回 void”,其实是想忽略结果,那就显式
await DoSomethingAsync().ConfigureAwait(false),而不是删掉返回类型
await 之后的代码不一定在原线程执行
很多人以为 await 只是“等完继续往下跑”,但它的线程归属取决于上下文:UI 线程有 SynchronizationContext,控制台或 ASP.NET Core 默认没有。这直接影响你能不能安全访问 UI 控件或 HttpContext。
使用场景:WPF 中更新 TextBox.Text,ASP.NET Core 中读取 HttpContext.Request。
- 默认情况下,
await后会尝试“回到原上下文”,但在无上下文环境(如 Console App)会直接切到线程池线程 - 如果不需要回上下文(比如纯计算、日志记录),加
.ConfigureAwait(false)避免不必要的调度开销和死锁风险 - ASP.NET Core 2.1+ 默认禁用了
HttpContext的跨线程访问,所以await后再读HttpContext很可能抛NullReferenceException,必须确保在 await 前读完所需数据
不要在构造函数里调用 async 方法
C# 不允许构造函数是 async 的,强行用 GetAwaiter().GetResult() 或 Wait() 会大概率引发死锁,尤其在 UI 或 ASP.NET 同步上下文中。
常见错误现象:界面假死、请求超时、线程池耗尽;调试时发现线程卡在 Task.Wait() 或 GetResult() 上。
- 构造函数必须同步完成,把异步初始化逻辑拆出去,改成工厂方法或
InitAsync()显式调用 - 如果要用“异步构造”,考虑用静态工厂方法,例如
public static async Task<MyService> CreateAsync() - 依赖注入容器(如 Microsoft.Extensions.DependencyInjection)不支持直接注册
async构造,需配合Lazy<Task<T>>或初始化接口(IAsyncInitialization)模式
Task.Run 不等于 async/await,乱套会白费力气
有人以为包一层 Task.Run(() => { /* 同步代码 */ }) 就算“异步”了,其实只是把 CPU 密集工作扔给线程池,既没释放当前线程,也没利用 I/O 异步优势,反而增加调度负担。
使用场景:真正需要并行计算(如图像处理)、或封装旧的同步阻塞 API(如某些数据库驱动没提供 async 版本)。
-
Task.Run适合 CPU-bound 工作;I/O 操作(HTTP、文件、数据库)优先用原生 async 方法(如HttpClient.GetAsync、FileStream.ReadAsync) - 在 ASP.NET Core 中滥用
Task.Run会快速吃光线程池,导致吞吐下降,监控能看到ThreadPool.GetAvailableThreads持续偏低 - 如果只是为了“不阻塞 UI”,WPF/WinForms 中
Task.Run+Dispatcher.Invoke是可行的,但务必注意捕获异常并传回 UI 线程处理
async 的核心不是“多线程”,而是“协作式让出与恢复”。最容易被忽略的是上下文切换的隐式成本,以及异常传播路径的断裂——这两点在复杂调用链里往往到上线才暴露。


















