【问题标题】:What is breaking my Process.StartInfo.OutputDataReceived callbacks prematurely?是什么过早地破坏了我的 Process.StartInfo.OutputDataReceived 回调?
【发布时间】:2017-04-26 14:18:29
【问题描述】:

在 .NET Framework v3.5 上出现以下问题。不知道是否适用于 v4*。

为了捕获某些进程的标准输出,我成功地使用了p.StartInfo.UseShellExecute = false;p.StartInfo.RedirectStandardOutput = true; 以及一个连接到p.StartInfo.OutputDataReceived+=...; 的事件处理程序。然后我打电话给p.Start(); 然后p.BeginOutputReadLine(); 然后p.WaitForExit();

到目前为止一切都很好。我按预期逐行获取事件处理程序上的所有标准输出。

我不得不引入超时而不是WaitForExit(),因为某些进程会意外触发标准输入的输入请求(例如,你确定吗?[y/n])导致我永远等待的死锁,他们也是。

我尝试的第一件事是更改为while (!p.HasExited && DateTime.Now < timeoutmom) p.WaitForExit(200);,其中timeoutmomentproc.Start() 之后2 分钟。这是我遇到问题的时候。非常一致的是,该代码适用于产生多达几百行标准输出的调用,但它会中断一个产生大约 7500 行的调用。发生的情况是 proc.WaitForExit(200); 线程退出 while 当我的 OutputDataReceived 事件处理程序仅被调用约 7300 行时(这个数字再次非常一致,它在测试之间仅变化 +/- 1)并且处理程序不是其余的标准输出行不再调用,所以我丢失了它们。

奇怪的是,如果我避免使用WaitForExit(200) 而是使用while (!p.HasExited && DateTime.Now < timeoutmom) System.Threading.Thread.Sleep(1000);(未在下面的代码中显示),问题就不会出现。当我发布问题时,我很确定问题避免使用Sleep(1000),但我错了。它像这样工作了几十次,然后它没有,它开始表现得就像我检查WaitForExit(200)时一样。

我现在推测这个问题的原因是(1)我处理每个OutputDataReceived回调的时间太长了。我注意到当我在事件处理程序中添加条件断点时问题更加严重,这大大延长了方法的执行时间。我现在可以通过简单地添加没有条件断点的 3x Debug.WriteLines 来重现该问题; PLUS (2) 在系统有机会在我的事件处理程序上执行所有回调之前,我访问 HasExited / WaitForExit(200) 以某种方式破坏了我的上下文。我现在在p.Start() 之后和访问任何p.* 方法之前做一个盲System.Threading.Thread.Sleep(30000),我得到了所有的回调。当我使用WaitForExit() 时,似乎我可以花很多时间来处理每个回调,但我仍然会得到它们。

有人能更明白这一点吗?

代码:

    private int _execOsProc(
        ProcessStartInfo Psi
        , string SecInsensArgs
        , TextWriter ExtraStdOutAndErrTgt
        , bool OutputToExtraStdOutOnly
        )
    {
        var pr = new Process();
        pr.StartInfo = Psi;
        pr.StartInfo.UseShellExecute = false;
        pr.StartInfo.RedirectStandardOutput = pr.StartInfo.RedirectStandardError = true;
        pr.StartInfo.CreateNoWindow = true;
        var ol = new DataReceivedEventHandler(this._stdOutDataReceived);
        var el = new DataReceivedEventHandler(this._stdErrDataReceived);
        pr.OutputDataReceived += ol;
        pr.ErrorDataReceived += el;
        try
        {
            __logger.Debug("Executing: \"" + pr.StartInfo.FileName + "\" " + SecInsensArgs);
            if (ExtraStdOutAndErrTgt == null)
            {
                this.__outputToExtraStdOutOnly = false;
            }
            else
            {
                this.__extraStdOutAndErrTgt = ExtraStdOutAndErrTgt;
                this.__outputToExtraStdOutOnly = OutputToExtraStdOutOnly;
            }
            pr.Start();
            pr.BeginOutputReadLine();
            pr.BeginErrorReadLine();
            var startmom = DateTime.Now;
            var timeoutmom = startmom.AddMinutes(2);
            while (!pr.HasExited && DateTime.Now < timeoutmom) pr.WaitForExit(200);
            pr.CancelOutputRead();
            pr.CancelErrorRead();
            if (pr.HasExited)
            {
                __logger.Debug("Execution finished with exit status code: " + pr.ExitCode);
                return pr.ExitCode;
            }
            else
            {
                __logger.Debug("Timeout while waiting for execution to finish");
                pr.Kill();
                return -100;
            }
        }
        finally
        {
            pr.OutputDataReceived -= ol;
            pr.ErrorDataReceived -= el;
            if (this.__extraStdOutAndErrTgt != null)
            {
                this.__extraStdOutAndErrTgt = null;
                this.__outputToExtraStdOutOnly = false;
            }
        }
    }

    private void _stdOutDataReceived(
        object sender
        , DataReceivedEventArgs e
        )
    {
        string rdata = string.IsNullOrEmpty(e.Data) ? "" : e.Data.Trim();
        if (!this.__outputToExtraStdOutOnly) __logger.Debug("SO: " + rdata);
        if (this.__extraStdOutAndErrTgt != null)
        {
            lock (this.__extraStdOutAndErrTgt)
            {
                try
                {
                    this.__extraStdOutAndErrTgt.WriteLine(rdata);
                    this.__extraStdOutAndErrTgt.Flush();
                }
                catch (Exception exc)
                {
                    __logger.Warn(
                                "WARNING: Error detected but ignored during extra stream write"
                                    + " on SODR. Details: " + exc.Message
                                , exc
                                );
                }
            }
        }
    }

    private void _stdErrDataReceived(
        object sender
        , DataReceivedEventArgs e
        )
    {
        string rdata = string.IsNullOrEmpty(e.Data) ? "" : e.Data.Trim();
        if (!__outputToExtraStdOutOnly) __logger.Debug("SE: " + rdata);
        if (this.__extraStdOutAndErrTgt != null)
        {
            lock (this.__extraStdOutAndErrTgt)
            {
                try
                {
                    this.__extraStdOutAndErrTgt.WriteLine(rdata);
                    this.__extraStdOutAndErrTgt.Flush();
                }
                catch (Exception exc)
                {
                    __logger.Warn(
                                "WARNING: Error detected but ignored during extra stream write"
                                    + " on SEDR. Details: " + exc.Message
                                , exc
                                );
                }
            }
        }
    }

【问题讨论】:

    标签: c# multithreading process stdout stderr


    【解决方案1】:

    我不确定它是否能解决问题,但是在评论中发布它太长了。

    MSDN 说Process.HasExited:

    当标准输出被重定向到异步事件时 处理程序,输出处理可能没有 当此属性返回 true 时完成。确保异步 事件处理已完成,调用 WaitForExit() 重载 在检查 HasExited 之前不接受任何参数。

    关于WaitForExit():

    此重载确保所有处理都已完成, 包括处理重定向标准的异步事件 输出。您应该在调用 重定向标准输出时的 WaitForExit(Int32) 过载 到异步事件处理程序。

    这表明,调用 WaitForExit() 不带参数应该可以解决问题。比如:

    var startmom = DateTime.Now;
    var timeoutmom = startmom.AddMinutes(2);
    while (!pr.HasExited && DateTime.Now < timeoutmom)
        pr.WaitForExit(200);
    
    if (pr.HasExited)
    {
        WaitForExit();//Ensure that redirected output buffers are flushed
    
        pr.CancelOutputRead();
        pr.CancelErrorRead();
    
        __logger.Debug("Execution finished with exit status code: " + pr.ExitCode);
        return pr.ExitCode;
    }
    else
    {
        pr.CancelOutputRead();
        pr.CancelErrorRead();
    
        __logger.Debug("Timeout while waiting for execution to finish");
    
        pr.Kill();
        return -100;
    }
    

    【讨论】:

    • 耶稣!我简直不敢相信……我遵循了 v3.5 的文档,其中该信息尚未被反向移植。它完全符合我对问题的描述。我会尝试一下,然后回来发表评论。最大的问题似乎是最终的 WaitForExit() 是否真的在 HasExited 变为 true 后刷新缓冲区。非常感谢!
    • 也许 .NET 3.5 中的行为有所不同,并且 WaitForExit() 调用不是必需的 - 例如,“WaitForExit(-1)”的行为在 .NET 3.5 之后也发生了变化。不幸的是,MSDN 没有明确描述在 HasExited==true 之后 WaitForExit() 的行为,所以你只能希望它会起作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-03
    • 2013-01-08
    • 2017-03-02
    • 1970-01-01
    相关资源
    最近更新 更多