.result 和 .wait() 在 ui 或 asp.net framework 中大概率死锁,因阻塞线程导致异步任务无法回调原上下文;asp.net core 无同步上下文故一般不死锁但仍有性能问题;应禁用并全面异步化调用链。

为什么 .Result 和 .Wait() 在 UI 或 ASP.NET 同步上下文里大概率死锁
它们会阻塞当前线程,等待异步任务完成;但若该任务内部又需要回到原上下文(比如 UI 线程或 ASP.NET 请求上下文)才能继续执行,而该上下文正被 .Result 占着不放,就形成循环等待。典型表现是调用卡住、CPU 为 0、调试器停在那一行不动。
- ASP.NET Core 默认无同步上下文,所以一般不会死锁(但仍有性能问题)
- ASP.NET Framework(.NET Framework 4.x)和 WinForms/WPF 默认有同步上下文,
.Result几乎必踩坑 - 即使加了
ConfigureAwait(false),也只对await生效,对.Result完全无效
如何快速定位代码中危险的 .Result 和 .Wait()
不是靠肉眼扫,而是用工具+约定双管齐下:
- 启用 Roslyn 分析器:安装
Microsoft.CodeAnalysis.FxCopAnalyzers(或更新的Microsoft.CodeAnalysis.NetAnalyzers),它会标出CA2007(不要直接 await Task)和CA2008(不要在没有 ConfigureAwait 的情况下启动任务)——虽然不直接报 .Result,但配合代码审查很有效 - 全局搜索:
.Result、.Wait()、.GetAwaiter().GetResult(),特别注意封装类、日志埋点、构造函数、属性 getter 中的调用 - 在 Startup 或 Program.cs 中临时注入同步上下文检测(仅开发期):
if (SynchronizationContext.Current != null && !(SynchronizationContext.Current is ThreadPoolSynchronizationContext)) { Debugger.Break(); }
替代方案:用 await + 重构调用链
不能只改一行,要顺藤摸瓜把整个调用栈“异步化”——从入口开始,一路 async/await 向上推。常见卡点:
- 事件处理器(如
Button_Click)可直接改为async void(仅限 UI 事件),但避免在其他地方用async void - 属性 getter/setter 不能是 async,需改为方法(如
GetUserAsync()),或用缓存 + 初始化任务(Lazy<task>></task>) - 构造函数不能是 async,可用工厂方法(
public static async Task<myservice> CreateAsync()</myservice>)代替 new - 想在同步方法里“安全”取结果?基本没得选——要么接受异步改造,要么用
Task.Run(() => asyncMethod()).Result(仅限 CPU 密集型且明确脱离上下文的场景,慎用)
不得已要用同步等待时的保底手段
极少数 legacy 集成或测试场景绕不开,必须用同步等待,那就主动剥离上下文:
- 用
Task.Run(() => someAsyncMethod()).Result—— 它把任务扔进线程池,脱离原始同步上下文 - 在 ASP.NET Framework 中,可在 Web.config 加
<httpruntime targetframework="4.7.2"></httpruntime>并升级到 .NET Framework 4.7.2+,部分缓解(非根治) - WPF/WinForms 中,可临时切换上下文:
var prev = SynchronizationContext.Current;<br>SynchronizationContext.SetSynchronizationContext(null);<br>try { var result = task.Result; }<br>finally { SynchronizationContext.SetSynchronizationContext(prev); }但极易遗漏恢复,不推荐
真正难的不是写对那几行代码,而是说服团队接受“异步不能只在底层用,必须穿透到顶层”。一旦某处用了 .Result,它就像一个污染源,会倒逼上游也放弃 async,最终整条链退化回同步模型。










