这篇只讲一个知识点在 .NET 代码里用.Result或GetAwaiter().GetResult()同步阻塞异步任务为什么在不同框架下会触发不同类型的事故。问题背景同样一行代码在两个系统里出现了完全不同的故障老系统ASP.NET MVC 5请求直接卡死不返回新系统ASP.NET Core不是直接死锁而是高峰期吞吐突然掉到很低请求排队超时两边都有这段写法publicstringGetData(){returnGetDataAsync().Result;}privateasyncTaskstringGetDataAsync(){awaitTask.Delay(50);returnok;}原理同一个坑两种后果场景 1ASP.NET Classic / WinForms / WPF有 SynchronizationContext这类框架默认要求 continuation 回到原上下文UI 线程或请求上下文。.Result先把当前线程阻塞住Task 完成后 continuation 又想回到这条线程结果互相等待当前线程在.Result处阻塞continuation 需要回到当前线程继续执行当前线程被阻塞continuation 进不来死锁所以你会看到请求一直转圈或界面完全卡死。场景 2ASP.NET Core默认无 SynchronizationContext在默认配置下ASP.NET Core 没有传统的请求级SynchronizationContext所以通常不会触发上面的经典互锁。它会把线程池工作线程同步阻塞住。并发一上来越来越多线程被卡在.Result线程池来不及补充新请求拿不到线程就出现线程饥饿CPU 不一定高数据库不一定慢但接口耗时和超时数暴涨这就是看起来不像死锁但系统几乎不可用的典型表现。最小对照示例publicsealedclassDemoService{// ❌ 错误同步包装异步publicintGetNumber(){returnGetNumberAsync().Result;}// ✅ 正确异步到底publicasyncTaskintGetNumberAsync(){awaitTask.Delay(10);return42;}}如何避坑只保留最关键三条不要在任何业务调用链上使用.Result/.Wait()/GetAwaiter().GetResult()。API、Service、Repository 全链路改成async不要做同步方法包异步。如果历史包袱必须保留同步签名就让边界层同步内部仍然异步避免层层阻塞传染。一句结论.Result在老框架里更容易直接死锁在 ASP.NET Core 里更容易演化成线程池饥饿表现不同本质相同都是阻塞等待异步导致的。
.NET .Result 避坑指南:不同框架下的死锁与线程池饥饿
这篇只讲一个知识点在 .NET 代码里用.Result或GetAwaiter().GetResult()同步阻塞异步任务为什么在不同框架下会触发不同类型的事故。问题背景同样一行代码在两个系统里出现了完全不同的故障老系统ASP.NET MVC 5请求直接卡死不返回新系统ASP.NET Core不是直接死锁而是高峰期吞吐突然掉到很低请求排队超时两边都有这段写法publicstringGetData(){returnGetDataAsync().Result;}privateasyncTaskstringGetDataAsync(){awaitTask.Delay(50);returnok;}原理同一个坑两种后果场景 1ASP.NET Classic / WinForms / WPF有 SynchronizationContext这类框架默认要求 continuation 回到原上下文UI 线程或请求上下文。.Result先把当前线程阻塞住Task 完成后 continuation 又想回到这条线程结果互相等待当前线程在.Result处阻塞continuation 需要回到当前线程继续执行当前线程被阻塞continuation 进不来死锁所以你会看到请求一直转圈或界面完全卡死。场景 2ASP.NET Core默认无 SynchronizationContext在默认配置下ASP.NET Core 没有传统的请求级SynchronizationContext所以通常不会触发上面的经典互锁。它会把线程池工作线程同步阻塞住。并发一上来越来越多线程被卡在.Result线程池来不及补充新请求拿不到线程就出现线程饥饿CPU 不一定高数据库不一定慢但接口耗时和超时数暴涨这就是看起来不像死锁但系统几乎不可用的典型表现。最小对照示例publicsealedclassDemoService{// ❌ 错误同步包装异步publicintGetNumber(){returnGetNumberAsync().Result;}// ✅ 正确异步到底publicasyncTaskintGetNumberAsync(){awaitTask.Delay(10);return42;}}如何避坑只保留最关键三条不要在任何业务调用链上使用.Result/.Wait()/GetAwaiter().GetResult()。API、Service、Repository 全链路改成async不要做同步方法包异步。如果历史包袱必须保留同步签名就让边界层同步内部仍然异步避免层层阻塞传染。一句结论.Result在老框架里更容易直接死锁在 ASP.NET Core 里更容易演化成线程池饥饿表现不同本质相同都是阻塞等待异步导致的。