.Result 和 .Wait() 在 UI 或 ASP.NET 同步上下文中会死锁,因它们阻塞线程导致 await 无法回调;应全程使用 async/await,必要时用 ConfigureAwait(false) 避免上下文捕获,禁用同步等待。
.Result 和 .Wait() 在 async 方法里会死锁在 UI 线程(如 WinForms/WPF)或 ASP.NET 同步上下文(如旧版 .NET Framework 的 AspNetSynchronizationContext)中,await 默认会尝试回到原上下文继续执行。而 .Result 或 .Wait() 会阻塞当前线程,导致上下文线程被占住;当异步操作完成、试图回调回该线程时,就卡住了——线程在等任务完成,任务又在等线程空闲,形成典型死锁。
await 替代同步等待最直接的解法是把调用链全部改为 async/await,让异步流自然穿透。常见错误写法和修正如下:
var data = GetJsonAsync().Result;(在 async Task 方法里也禁用)var data = await GetJsonAsync();
async void(仅限事件处理器),但避免在其他地方用 async void
GetAwaiter().GetResult(),它不
捕获同步上下文,但依然会阻塞线程,仅作兜底ConfigureAwait(false) 能缓解但不能根治加 ConfigureAwait(false) 可以让 await 不尝试回到原始上下文,从而避免 UI/ASP.NET 中的死锁。但它只对 await 生效,对 .Result 和 .Wait() 完全无效——这两个方法本身就会强制同步阻塞,跟配置无关。
示例:
var task = DoWorkAsync(); // 即使下面用了 ConfigureAwait,.Result 仍会死锁 // ❌ var result = task.ConfigureAwait(false).GetAwaiter().GetResult(); // 错!还是阻塞 // ✅ var result = await task.ConfigureAwait(false); // 对
即使在没有同步上下文的环境(如 .NET Core 控制台程序),滥用 .Result 和 .Wait() 仍有隐患:
AggregateException,堆栈信息被截断,调试困难CancellationToken 会被忽略)真正难处理的不是“怎么让它跑起来”,而是“为什么它偶尔卡住、有时抛奇怪异常、上线后吞吐骤降”——这些问题往往都藏在某个不起眼的 .Result 调用里。