异步 C# StartInfo 进程仅当 stderr 未重定向时才会转储 stdout 行?

Asynchronous C# StartInfo process dumps stdout lines, only if stderr is not redirected?

提问人:sdbbs 提问时间:9/9/2023 更新时间:9/9/2023 访问量:21

问:

我正在开发一个运行进程的 C# 应用程序,并且输出行根据 c# 运行 CL exe 或批量 cpture 输出 LIVE 到 textbox? 实时(异步)输出,代码如下:

    Process new_process = new Process();

    new_process.StartInfo.FileName = tool_exe;
    new_process.StartInfo.Arguments = tool_arguments;
    new_process.StartInfo.RedirectStandardInput = true;
    new_process.StartInfo.RedirectStandardOutput = true;
    //new_process.StartInfo.RedirectStandardError = true;
    new_process.StartInfo.UseShellExecute = false;
    new_process.StartInfo.CreateNoWindow = true;
    string line;
    
    void _cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        line = e.Data;
        MyAppendTextBox1(line);
    }
    void _cmd_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        MyAppendTextBox1("err: " + e.Data);
    }
    bool did_start = new_process.Start();
    if (did_start)
    {
        new_process.OutputDataReceived += new DataReceivedEventHandler(_cmd_OutputDataReceived);
        new_process.ErrorDataReceived += new DataReceivedEventHandler(_cmd_ErrorDataReceived);
        //new_process.Exited += new EventHandler(_cmd_Exited);
        new_process.BeginOutputReadLine();
        new_process.BeginErrorReadLine();
    }

    new_process.WaitForExit();

现在,问题是,似乎可能会用 stderr 和 stdout 重定向做一些奇怪的事情......所以我观察到的是:tool_exe

  • 如果 RedirectStandardError 不为 true(行注释) - 那么在运行时,我得到:
System.InvalidOperationException: StandardError has not been redirected.
   at System.Diagnostics.Process.BeginErrorReadLine()

...但除此之外,代码运行良好,文本框实时更新,进程成功退出。

  • 但是,如果我尝试通过取消注释该行并设置new_process来纠正该异常。StartInfo.RedirectStandardError = true - 然后,当程序运行时,只打印一行(_cmd_OutputDataReceived只触发一次),然后 C# 程序似乎冻结,进程似乎没有退出(所以我必须从 Windows 任务管理器关闭所有内容)?!

我无法想象为什么会发生这种行为(为什么它只有在我遇到异常时才有效)?!

我唯一注意到的是,上面的代码在点击处理程序的上下文中运行;当使用 RedirectStandardError = true 运行构建时,当它冻结时,我实际上在输出中看到了一些我原本期望的消息,显示在 VS2019 的调试输出窗口中(可能在调试对象关闭之后)?

有谁知道这里发生了什么,我怎样才能无一例外地进行正常操作?

C# 异步 IO 重定向

评论

1赞 Theodor Zoulias 9/9/2023
在开始之前,您是否尝试过在 etc 事件上附加处理程序?OutputDataReceivedProcess
1赞 sdbbs 9/9/2023
谢谢@TheodorZoulias - 只是尝试了一下,它没有区别;另外,奇怪的是,如果被评论,我也评论以避免异常,那么我被冻结了?!RedirectStandardError = true;new_process.BeginErrorReadLine();
1赞 Etienne de Martel 9/9/2023
何时退出?如果答案是“从不”,那么,是的,你将永远冻结在 .如果你在未设置为 true 的情况下调用,那么你会得到一个异常,但该异常发生在你开始读取标准输出之后,因此它“有效”(但执行永远不会达到,所以没有“冻结”)。据推测,你在某个地方捕捉到了异常,否则你会崩溃。new_processWaitForExit()BeginErrorReadLine()RedirectStandardErrorWaitForExit()
0赞 sdbbs 9/9/2023
感谢@EtiennedeMartel - 一般来说,在 10 秒到一分钟之间的任何时间后退出 - 但是,当在调试器中遇到上述问题时,它似乎一直挂起(因为我在 C# 应用程序中没有看到它的任何输出,正如我所指出的)。但是,我做了更多的实验,并无一例外地打印了 stderr 和 stdout,如下面的答案中所述new_process
0赞 Theodor Zoulias 9/9/2023
你能在问题中包括方法吗?另外,请指定整个代码所在的位置(创建 的代码)。它是否位于 UI 控件的事件处理程序中?MyAppendTextBox1new_process

答:

0赞 sdbbs 9/9/2023 #1

似乎通过以这种方式重新组织代码,我可以无一例外地在 stdout 和 stderr 正确重定向的情况下运行它(但通过以下更改,WaitForExit() 不再起作用:

    Process new_process = new Process();

    new_process.StartInfo.FileName = tool_exe;
    new_process.StartInfo.Arguments = tool_arguments;
    new_process.EnableRaisingEvents = true; // https://stackoverflow.com/q/4504170
    new_process.StartInfo.RedirectStandardInput = true;
    new_process.StartInfo.RedirectStandardOutput = true;
    new_process.StartInfo.RedirectStandardError = true;
    new_process.StartInfo.UseShellExecute = false;
    new_process.StartInfo.CreateNoWindow = true;
    string line;
    
    void _cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        line = e.Data;
        MyAppendTextBox1(line);
    }
    void _cmd_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        MyAppendTextBox1("err: " + e.Data);
    }
    void _cmd_Exited(object sender, EventArgs e)
    {
        new_process.OutputDataReceived -= new DataReceivedEventHandler(_cmd_OutputDataReceived);
        new_process.ErrorDataReceived -= new DataReceivedEventHandler(_cmd_ErrorDataReceived);
        new_process.Exited -= new EventHandler(_cmd_Exited);
    }

    new_process.OutputDataReceived += new DataReceivedEventHandler(_cmd_OutputDataReceived);
    new_process.ErrorDataReceived += new DataReceivedEventHandler(_cmd_ErrorDataReceived);
    new_process.Exited += new EventHandler(_cmd_Exited);

    new_process.Start();
    new_process.BeginOutputReadLine();
    new_process.BeginErrorReadLine();