上位机业务卡死问题windbg分析

问题背景

今年 7 月份,现场反馈了一个上位机卡死的问题:运行的流程一直没有结束,但日志却没有持续输出,只剩心跳包在打印。

当时我们抓取了两个 dump 文件,打印了所有线程的调用堆栈,试图从中定位卡死的位置。然而分析完所有线程后,始终没有找到业务线程的踪影。

由于情况紧急必须尽快修复,我们只能结合卡住前的日志信息,用了一个晚上逐行分析代码,最终找到了卡死的代码位置。但这也留下了一个疑惑:卡死的地方明明是一个 while 循环,为什么 WinDbg 打印的所有线程调用堆栈中却找不到这个函数?

过了一个多月,最近终于闲下来,可以仔细钻研一下这个问题了。

复现 Demo

以下是我简化后的 demo 代码:

c# 复制代码
private void TestBtn_Click(object sender, RoutedEventArgs e)
{
    Task.Run(async () =>
    {
        await TestFun();
    });
}

bool flag = false;

async Task TestFun()
{
    while (true)
    {
        await Task.Delay(100);
        if (flag)
        {
            Debug.WriteLine("Flag is true, exiting loop.");
            break;
        }
    }
}

代码逻辑很简单:点击 Test 按钮后,通过线程池线程执行 TestFun 函数。需要注意的是,TestFun 并不是一个普通函数,而是一个异步函数 ------在 while 循环中交替执行 await Task.Delay(100) 和业务代码。

现象复现与原因分析

运行程序、点击按钮后,用任务管理器抓取 dump,再用 WinDbg 分析所有线程的调用堆栈------果然,没有找到任何正在执行 TestFun 的线程。

原因就在"异步"二字上:

异步函数在编译时会被编译器转换为一个状态机 。当执行到 await Task.Delay 时,当前线程会被释放 回线程池,此时它要么空闲,要么去执行其他任务。状态机会挂起等待,直到 Task.Delay 完成后,再由某个线程池线程(不一定是原来的线程)继续执行后续代码。

因此,抓取 dump 的那一刻,如果状态机正好处于 Task.Delay 的等待阶段,就没有任何一个线程 的调用堆栈上能看到 TestFun------它根本不在任何线程上运行。这正是当初在 dump 中找不到业务线程的根本原因。

生产环境如何定位

那么,生产环境遇到这类问题该如何定位呢?答案是使用 SOS 扩展的 !dumpasync 命令,它可以枚举出当前所有处于挂起状态的异步状态机:

xml 复制代码
0:000> !dumpasync
STACK 1
000002550ba48670 00007ffe05e64998 ( ) System.Threading.Tasks.Task+DelayPromise
  000002550b9e2480 00007ffe05e67bb0 (0) WhileLoopDemo.MainWindow+<TestFun>d__3 @ 7ffe05d8a200
    000002550b9e24d0 00007ffe05e685e0 (0) WhileLoopDemo.MainWindow+<<TestBtn_Click>b__1_0>d @ 7ffe05d89ee0
      000002550b9df440 00007ffe05e49cd8 ( ) System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>

从输出可以清晰地看到异步调用链:TestFun 状态机(<TestFun>d__3)正挂起在 Task.DelayPromise 上,其上层是按钮点击事件的 lambda 状态机。即便没有线程正在执行它,异步调用链依然一览无余。

总结

以后再遇到"程序卡死但 dump 中找不到业务线程"的问题时,不妨换个思路:

  1. 先怀疑业务代码中是否存在 while 死循环 (尤其是配合 await 的异步循环);
  2. 除了查看常规的线程调用堆栈,别忘了用 !dumpasync 查看异步状态机------真正的"案发现场"可能根本不在任何线程上。
相关推荐
duyacode2 天前
FastAPI为什么成为默认答案
程序员
Java_AI工程师2 天前
90%的人写Function Calling,只写了"把参数传给工具执行"这一步,剩下的参数校验、错误重试、超时控制、结果格式化,全是空白。
java·人工智能·程序员
Hilaku2 天前
为什么死磕 1px 的团队,用户体验反而更差?
前端·javascript·程序员
codigger2 天前
我用 AI 做完整项目后,总结出一套把需求钉死的工作流
ai·程序员·编程·ai编程·#人工智能
SimonKing2 天前
SpringBoot 集成 SSE 实现服务端推送或可代替Websocket
java·后端·程序员
挖掘狂人2 天前
用 AI 写代码别只甩一句指令,这套工作流救过我的项目
程序员·ai编程·codebuddy
Sam_Deep_Thinking2 天前
关于java final关键字的可见性
java·后端·面试·程序员
狂师2 天前
cloudflared,不需要服务器和公网 IP 的免费内网穿透,一条命令上手
服务器·程序员·开源
demo007x3 天前
我用 AI 做了一个跨平台的PopClip:拾趣Magpie
程序员·github·ai编程
zzzzzz3103 天前
每日一条技术记录:把碎片学习变成可复盘的知识卡片
程序员·markdown·沸点