
为什么 GetAwaiter().GetResult()
是最常踩的坑
它看起来像同步调用,实际会阻塞当前线程并可能引发死锁——尤其在 UI 线程或 ASP.NET 同步上下文里。比如在 WinForms 事件处理器中写
var result = SomeAsyncMethod().GetAwaiter().GetResult();,UI 线程被占住,而异步操作内部又尝试回调到该线程,直接卡死。 它不释放上下文,也不支持取消,错误堆栈也难追踪 和
.Result行为一致,但后者还可能包装
AggregateException,更难调试 仅适合极少数场景:控制台主函数入口、单元测试 setup(且需确保无同步上下文)
ASP.NET Core 中 async void
为什么比 async Task
更危险
async void方法无法被等待,异常会直接抛给 SynchronizationContext,导致进程崩溃或静默丢失;而 ASP.NET Core 默认没有全局异常处理器捕获这类异常。 控制器动作方法必须返回
Task或
Task<t></t>,返回
void会绕过框架的请求生命周期管理 事件处理(如
Button.Click += async void (...) {...})在 WebAssembly 或 Blazor Server 中同样不可靠
日志里看不到未捕获异常,监控也收不到,问题暴露滞后
如何安全地“同步等待”一个 Task
(真有这需求时)
绝大多数情况不该这么做。但如果集成旧代码、测试断言或极简 CLI 工具,且明确知道运行环境无同步上下文(如 .NET 6+ 控制台应用),可用以下方式:
var task = SomeAsyncMethod(); task.Wait(); // 比 GetResult() 稍好:异常原样抛出,不包 AggregateException // 或更稳妥: task.ConfigureAwait(false).GetAwaiter().GetResult(); // 显式放弃上下文捕获
ConfigureAwait(false)是关键,它告诉编译器“别试图回到原始上下文”,避免死锁 仍要配合
try/catch处理
TaskCanceledException和业务异常 永远不要在库代码中暴露同步包装方法(如
DoSomething()+
DoSomethingAsync()),这会把阻塞风险传递给调用方
Task.Run(() => { ... }).Result 真的安全吗?
不安全。它只是把同步阻塞移到了线程池线程上,看似没卡主线程,实则浪费线程资源、增加调度开销,并可能引发线程池饥饿——尤其高并发场景下大量
Task.Run(...).Result会拖垮整个应用吞吐。 它不是“异步转同步”的正确解法,而是用资源换表象 若原方法本就 CPU 密集,应改用
Task.Run+
await,而不是
.ResultASP.NET Core 中禁止在请求处理路径里调用它,中间件或过滤器里出现会被诊断工具标记为高风险 真正棘手的从来不是语法怎么写,而是判断「这个调用链里有没有同步上下文」「下游是否支持取消」「异常传播路径是否可控」——这些没法靠加个
await就自动解决。
