直接结论:别在 UI 线程或 ASP.NET 同步上下文里用 GetAwaiter().GetResult()
和 .Result
它们都会同步阻塞线程,极易引发死锁;
await是唯一推荐的异步等待方式。不是“风格不同”,而是“安全 vs 危险”的分水岭。
.Result
和 GetAwaiter().GetResult()
实际行为几乎一样,但后者更底层、更易暴露异常细节
两者都强制同步等待
Task完成,并解包结果(或抛出
AggregateException包裹的原始异常)。区别在于:
.Result是
Task<tresult></tresult>的属性,内部调用了
GetAwaiter().GetResult()
GetAwaiter().GetResult()是
Task和
Task<tresult></tresult>共享的底层方法,异常不被二次包装——比如
OperationCanceledException会原样抛出,而
.Result总是包进
AggregateException二者在同步上下文(如 WinForms 主线程、ASP.NET Classic)中调用时,都会尝试捕获当前
SynchronizationContext,等待期间阻塞线程,导致后续回调无法调度,最终死锁
await
不是语法糖,它重写了控制流并避免线程阻塞
await编译后会把方法拆成状态机,挂起当前执行点,把延续(continuation)注册为
Task完成后的回调,不占用线程资源。关键差异: 不会阻塞线程,适合高并发场景(如 Web API) 自动传播取消信号(配合
CancellationToken) 异常直接抛出,无需解包
AggregateException要求方法标记为
async,返回
Task或
Task<t></t>,调用链必须“异步穿透”——不能在中间某层突然用
.Result截断
public async Task<string> FetchDataAsync()
{
// ✅ 正确:await 让出线程,完成后自然继续
var response = await HttpClient.GetAsync("https://api.example.com/data");
return await response.Content.ReadAsStringAsync();
}
<p>// ❌ 危险:在 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()) 绝对不要在 ASP.NET(含 Core 的
Controller)、WinForms/WPF 事件处理、Blazor 服务中出现
即使在此类“安全”场景,也建议优先用
await+
async Main,避免养成坏习惯。一旦代码挪到有上下文的环境,就埋下死锁隐患。
