C#异步死锁排查3小时,最后发现是Wait()的锅
侧边栏壁纸
  • 累计撰写 1,021 篇文章
  • 累计收到 3 条评论

C#异步死锁排查3小时,最后发现是Wait()的锅

私人云
2026-01-29 / 0 评论 / 0 阅读 / 正在检测是否收录...

用了async/await结果死锁了,原来是这个原因

上周线上接口突然卡死,所有请求都超时,重启服务器才恢复。排查了3个小时,最后发现是async/await用错了,导致死锁。

这个坑90%的C#开发者都踩过,今天把原理和解决方案讲清楚。

问题代码长这样:

public ActionResult Index(){// 错误写法:在同步方法里用 .Result 等待异步方法var data = GetDataAsync().Result;return View(data);}public async Task<string> GetDataAsync(){await Task.Delay(1000); // 模拟异步操作return "数据";}

看起来没问题对吧?但运行后直接卡死,界面一直转圈,永远不返回。

为什么会死锁?

这里涉及到同步上下文(SynchronizationContext)的概念:

1. ASP.NET 请求进来时,会创建一个同步上下文,确保代码在同一个线程执行

2. 调用 GetDataAsync().Result 时,主线程被阻塞,等待异步方法完成

3. 异步方法执行到 await Task.Delay(1000) 时,会释放线程去做其他事

4. 1秒后,异步方法想继续执行,需要回到原来的同步上下文

5. 但是!原来的线程还在等 .Result,被阻塞了

6. 异步方法等线程,线程等异步方法,死锁了

解决方案有3种:

方案1:全链路async(推荐)

// 正确写法:从头到尾都用 async/awaitpublic async Task<ActionResult> Index(){var data = await GetDataAsync(); // 用 await,不用 .Resultreturn View(data);}public async Task<string> GetDataAsync(){await Task.Delay(1000);return "数据";}

方案2:ConfigureAwait(false)

public ActionResult Index(){// 如果改不了调用方,可以在异步方法里加 ConfigureAwait(false)var data = GetDataAsync().Result;return View(data);}public async Task<string> GetDataAsync(){// 加上 ConfigureAwait(false),不回到原同步上下文await Task.Delay(1000).ConfigureAwait(false);return "数据";}

方案3:Task.Run包装(不推荐)

public ActionResult Index(){// 用 Task.Run 包一层,在线程池执行var data = Task.Run(() => GetDataAsync()).Result;return View(data);}

避坑指南:

1. 能用 await 就别用 .Result 或 .Wait()

2. ASP.NET Core 没这个问题(没有同步上下文)

3. WinForms/WPF 也会死锁,原理一样

4. 类库代码建议都加 ConfigureAwait(false)

实战数据对比:

我们线上接口优化前后对比:

• 优化前:接口超时率 100%,服务器CPU 0%(都在等)

• 优化后:接口响应时间 200ms,吞吐量提升 50 倍

就改了一个词,从 .Result 改成 await,效果立竿见影。

记住一句话:

async/await 要么全用,要么别用,千万别混着用

遇到死锁,90% 是因为在同步方法里用了 .Result 或 .Wait(),改成 await 就好了。

#程序员##编程#

0

评论 (0)

取消