EF Core 避坑:.Result 在不同框架下的死锁与线程饥饿

这篇文章讲解在 EF Core 调用链里使用 .Result(或 GetAwaiter().GetResult()),为什么在不同 .NET 框架下会表现成两种事故。

问题背景

同样一行代码,在两个系统里出现了完全不同的故障:

  • 老系统(ASP.NET MVC 5)请求直接卡死,不返回
  • 新系统(ASP.NET Core)不是直接死锁,而是高峰期吞吐突然掉到很低,请求排队超时

两边都有这段写法:

csharp 复制代码
public Order? GetOrder(long id)
{
    // 典型坑:同步方法里阻塞异步 EF Core 调用
    return _db.Orders.FirstOrDefaultAsync(x => x.Id == id).Result;
}

原理:同一个坑,两种后果

场景 1:ASP.NET Classic / WinForms / WPF(有 SynchronizationContext)

这类框架默认要求 continuation 回到原上下文(UI 线程或请求上下文)。

.Result 先把当前线程阻塞住,Task 完成后 continuation 又想回到这条线程,结果互相等待:

  1. 当前线程在 .Result 处阻塞
  2. continuation 需要回到当前线程继续执行
  3. 当前线程被阻塞,continuation 进不来
  4. 死锁

所以你会看到"请求一直转圈"或"界面完全卡死"。

场景 2:ASP.NET Core(默认无 SynchronizationContext)

在没有带 SynchronizationContext 的代码中, ASP.NET Core 不会触发上面的经典死锁,但 .Result 依然很危险。

它会把线程池工作线程同步阻塞住。并发一上来,越来越多线程被卡在 .Result,线程池来不及补充,新请求拿不到线程,就出现线程饥饿:

  • CPU 不一定高
  • 数据库不一定慢
  • 但接口耗时和超时数暴涨

这就是"看起来不像死锁,但系统几乎不可用"的典型表现。

EF Core 里的最小复现

csharp 复制代码
public sealed class OrderService
{
    private readonly AppDbContext _db;

    public OrderService(AppDbContext db)
    {
        _db = db;
    }

    // ❌ 错误:同步包装异步
    public Order? GetById(long id)
    {
        return _db.Orders.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id).Result;
    }

    // ✅ 正确:异步到底
    public Task<Order?> GetByIdAsync(long id, CancellationToken ct)
    {
        return _db.Orders.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, ct);
    }
}

如何避坑(只保留最关键三条)

  1. 不要在任何 EF Core 调用链上使用 .Result / .Wait() / GetAwaiter().GetResult()。
  2. API、Service、Repository 全链路改成 async,不要做"同步方法包异步"。
  3. 除非外部接口强制要求同步签名,且你能控制并发量,否则优先异步。

一句结论

.Result 在老框架里更容易直接死锁,在 ASP.NET Core 里更容易演化成线程饥饿;表现不同,本质相同,都是"阻塞等待异步"导致的。

相关推荐
goehou4 天前
ReAct 循环跑偏的三种样子,以及怎么在工程上兜住
llm·agent·react·工具调用·工程实践
胖纸不争5 天前
EnergyStarZ:让 Windows 后台进程给前台让路的能效管理器(v1.2 发布)
windows·性能优化·c#·win32·ef core
小七-七牛开发者7 天前
Meta 如何改造工程环境,让 Agent 自己跑开发 Loop
meta·agent·工程实践·ai 编程·研发工具链
goehou10 天前
Agent 把用户地址发给了模型,数据脱敏该在哪一层做
llm·agent·数据脱敏·隐私合规·工程实践
围炉聊科技13 天前
AI原生SDLC实践笔记
智能体·sdlc·工程实践
把我格子衫拿来17 天前
VTK 与 Qt 的 CMake 工程怎么组织?模块、工具链与二进制兼容排错
c++·qt·vtk·cmake·工程实践
qq_1588954111124 天前
声音相似度检测的工程实现:声纹与音频指纹的区别
声纹识别·音频处理·工程实践
上位机妹子25 天前
EF Core 两种继承映射:每层次一张表(TPH)与每类型一张表(TPT)
数据库·c#·.netcore·ef core
咖啡星人k1 个月前
MCP 工具调用的成本控制:按量计费下的四个止损点
工程实践·mcp·成本控制
CSharp精选营2 个月前
你以为加个 TenantId 字段就算隔离了,其实还差三道防线
ef core·saas安全·多租户隔离·tenantid·全局查询过滤器