You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core进程读取子进程输出死锁,如何用WinDBG排查?

排查.NET跨进程I/O死锁(stdout/stderr缓冲区阻塞导致)

问题背景

遇到了微软文档中提到的父进程读取子进程stdout和stderr时的死锁场景,自身无法复现但同事可以,增加了排查难度。

死锁发生代码

死锁出现在以下代码的process.StandardOutput.ReadLineAsync方法中:

public async Task<ProcessRunnerResult> Run(string executablePath,
    IProcessRunner.Mode mode,
    string? args = null,
    string? workingDirectory = null,
    Action<ProcessStartInfo>? beforeInvoke = null,
    Action<string>? notifyStdOutLine = null,
    CancellationToken cancellationToken = default)
{
    var process = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = executablePath,
            Arguments = args,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true,
            UseShellExecute = false,
            WorkingDirectory = workingDirectory
        }
    };

    string? stdErr = null;
    try
    {
        beforeInvoke?.Invoke(process.StartInfo);

        process.Start();

        string? line;
        while ((line = await process.StandardOutput.ReadLineAsync(cancellationToken)) != null)
        {
            notifyStdOutLine?.Invoke(line);
        }

        stdErr = await process.StandardError.ReadToEndAsync(cancellationToken);
    }
    catch (Exception ex)
    {
        if (mode == IProcessRunner.Mode.NeverThrow)
        {
            return new ProcessRunnerResult(-1, null, ExceptionDispatchInfo.Capture(ex));
        }
        throw new ProcessRunnerException(executablePath, args, -1, ex);
    }

    if (mode == IProcessRunner.Mode.RethrowAndIfNonZeroExitCode && process.ExitCode != 0)
    {
        throw new ProcessRunnerException(executablePath, args, process.ExitCode);
    }

    return new ProcessRunnerResult(process.ExitCode, stdErr);
}

死锁原因推测

父进程在循环读取stdout的过程中阻塞,而子进程向stderr输出的内容填满了缓冲区,子进程因无法继续写入stderr而阻塞,最终导致双向死锁:父进程等待子进程输出stdout,子进程等待父进程读取stderr。

利用WinDBG和完整内存Dump排查步骤

此类死锁属于跨进程I/O阻塞死锁,并非托管代码内的锁竞争,因此!sos.syncblk无法捕获有效信息,且sosex不支持.NET Core,需通过以下步骤分析父、子进程的Dump:

1. 父进程Dump分析

  • 定位阻塞线程:执行!clrstack -all查看所有线程的托管调用栈,找到卡在Process.StandardOutput.ReadLineAsync的线程,确认该线程处于等待状态。
  • 分析异步任务:执行!dumpasync列出所有未完成的异步任务,定位到与stdout/stderr读取相关的任务,查看任务的等待来源与状态。
  • 检查Process实例状态:
    1. 执行!dumpheap -type System.Diagnostics.Process列出所有Process对象实例;
    2. 对目标实例执行!do <实例地址>,查看其StandardOutput、StandardError对应的流对象状态,确认是否存在未完成的阻塞读取操作。
  • 查看原生调用栈:执行k(原生栈)或!dumpstack命令,确认线程是否卡在Win32的ReadFile等读取API上,验证是在等待子进程的输出数据。

2. 子进程Dump分析

  • 定位输出阻塞线程:执行!threads或k命令,找到负责向stderr输出内容的线程,查看其是否卡在WriteFile等输出API上,确认是因缓冲区满导致阻塞。
  • 检查stderr缓冲区状态:
    1. 执行!handle 0 f列出所有进程句柄,找到对应stderr的句柄;
    2. 执行!handle <stderr句柄> f查看句柄的详细信息,确认缓冲区已被填满。

补充说明

已提供问题复现案例:master分支为存在死锁的代码,fix分支为修复后的版本。

内容的提问来源于stack exchange,提问作者mark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 06:15:10