1..NET8依赖注入新特性Keyed services2.C#12中的Primary Constructors(主构造函数)3..NET8 Blazor的Auto渲染模式的初体验4..NET8 Blazor新特性 流式渲染5.C#12中的Collection expressions(集合表达式语法糖)6.将 .NET Aspire 部署到 Kubernetes 集群7.WinForm 开源组件 Realtiizor8..NET 中 Logger 常被忽视的方法 BeginScope9..NET中优雅使用Patch: JsonPatch10.dotnet-trace 分析.net程序性能11.用 Sidecar 容器为 .NET Core 应用做诊断和性能分析12.WinDbg 分析 .NET Dump 线程锁问题
13.windbg 分析线程死锁
正文
整套ASP.NET线程卡死分析步骤总结(标准化排查套路)
一、第一步:宏观看整体指标 !tp
- 查看线程池Worker总数、Running/Idle、排队队列;
- 特征:Running=总线程、Idle=0、队列积压几千 → 大批量线程永久卡死不释放。
- 初步结论:不是瞬时并发高,是线程泄漏卡死。
二、第二步:排查托管锁 !syncblk
- 看每条:
MonitorHeld=占用数、有无等待的Waiter线程 - ① 有大量等待线程 = 多lock互相争抢,传统Monitor死锁
- ② 只有占用、无等待线程 = 线程拿着锁卡在业务代码,锁是副产品,不是元凶(你的场景)
三、第三步:抽卡死线程栈(从syncblk里提取OS-TID)
~~[0xTID]s;!clrstack 打印单个卡死线程托管堆栈:
- 栈出现:
ManualResetEventSlim.Wait → Task.InternalWait → Task.Wait()
→ 锁定:同步.Wait()阻塞异步任务,上下文死锁。 - 向上追溯业务类:定位出事方法(ServiceDiscovery、MatrixHelper、HttpModule、Controller)。
四、第四步:结合代码解释「偶发正常、随机卡死」
- 命中缓存/连接复用:
GetAsync同步执行完毕,返回已完成Task,await不放线程,全链路顺行,正常释放; - 真实跨网IO:返回未完成Task,
await释放工作线程;IO完成回调要切回AspNetSynchronizationContext,但主线程被.Wait()占死 → 闭环死锁、线程永久滞留。
五、第五步:区分两种死锁、落地修复
- 传统lock死锁:A拿锁1等锁2、B拿锁2等锁1 → 改锁顺序、缩小lock范围;
- ASP上下文死锁(本次故障):同步
.Wait()/Result+await无ConfigureAwait(false)
修复二选一:- 方案1:全链路改成async/await,删除所有
.Wait(); - 方案2:所有
await xxx.ConfigureAwait(false),切断回调绑定请求上下文。
- 方案1:全链路改成async/await,删除所有
精简口诀
!tp看池子满不满,!syncblk辨锁死还是卡死,切线程栈找Wait,缓存决定偶发好坏
