别在 UI 线程或 ASP.NET 同步上下文中用 GetAwaiter().GetResult() 和 .Result,二者均会同步阻塞线程引发死锁;await 是唯一安全的异步等待方式,它不阻塞线程、自动传播取消信号且异常直接抛出。
GetAwaiter().GetResult() 和 .Result
它们都会同步阻塞线程,极易引发死锁;await 是唯一推荐的异步等待方式。不是“风格不同”,而是“安全 vs 危险”的分水岭。
.Result 和 GetAwaiter().GetResult() 实际行为几乎一样,但后者更底层、更易暴露异常细节两者都强制同步等待 Task 完成,并解包结果(或抛出 AggregateException 包裹的原始异常)。区别在于:
.Result 是 Task 的属性,内部调用了 GetAwaiter().GetResult()
GetAwaiter().GetResult() 是 Task 和
Task 共享的底层方法,异常不被二次包装——比如 OperationCanceledException 会原样抛出,而 .Result 总是包进 AggregateException
SynchronizationContext,等待期间阻塞线程,导致后续回调无法调度,最终死锁await 不是语法糖,它重写了控制流并避免线程阻塞await 编译后会把方法拆成状态机,挂起当前执行点,把延续(continuation)注册为 Task 完成后的回调,不占用线程资源。关键差异:
CancellationToken)AggregateException
async,返回 Task 或 Task,调用链必须“异步穿透”——不能在中间某层突然用 .Result 截断public async TaskFetchDataAsync() { // ✅ 正确:await 让出线程,完成后自然继续 var response = await HttpClient.GetAsync("https://api.example.com/data"); return await response.Content.ReadAsStringAsync(); } // ❌ 危险:在 ASP.NET MVC Action 中这样写大概率死锁 public string GetData() { return FetchDataAsync().Result; // 阻塞请求线程,等待回调 → 回调等不到线程 → 死锁 }
.Result / GetAwaiter().GetResult() 场景:控制台主函数或无上下文环境仅当确定当前线程没有 SynchronizationContext(如 .NET Core/.NET 5+ 控制台程序、单元测试中默认 TaskScheduler)且你**明确需要同步等待**(极少见),才可谨慎使用:
Main 方法(C# 7.1+ 支持 async Main,优先用它)Task.Run(...).Wait())Controller)、WinForms/WPF 事件处理、Blazor 服务中出现即使在此类“安全”场景,也建议优先用 await + async Main,避免养成坏习惯。一旦代码挪到有上下文的环境,就埋下死锁隐患。