是什么打破了我的Process.StartInfo.OutputDataReceived回调过早?

时间:2016-12-11 16:52:42

标签: c# multithreading process stdout stderr

.NET Framework v3.5上发生以下问题。不知道它是否适用于v4 *。

要捕获某些进程的stdout,我已成功使用p.StartInfo.UseShellExecute = false;p.StartInfo.RedirectStandardOutput = true;以及一个挂钩到p.StartInfo.OutputDataReceived+=...;的事件处理程序。然后我拨打p.Start();然后p.BeginOutputReadLine();然后p.WaitForExit();

到目前为止一切都很顺利。我按预期在行事处理程序上获得所有stdout。

我不得不引入超时而不是WaitForExit(),因为某些进程无法预测地触发stdin上的输入请求(例如,你确定吗?[y / n])导致死锁,我永远等待,所以他们也是

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

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

我现在推测这个问题的原因是(1)我花了太长时间来处理每个OutputDataReceived回调。我注意到当我在事件处理程序中添加一个条件断点时,问题变得更加严重,这会延长方法执行次数。我现在可以通过简单地添加没有条件断点的3x Debug.WriteLines来重现问题;加上(2)我的上下文因为在系统有机会在我的事件处理程序上执行所有回调之前访问HasExited / WaitForExit(200)而以某种方式损坏。我现在在System.Threading.Thread.Sleep(30000)之后和访问任何p.Start()方法之前盲目p.*,我得到了所有的回调。当我使用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
                                );
                }
            }
        }
    }

1 个答案:

答案 0 :(得分:0)

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

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;
}