stdout和stderr监听器问题(导致冻结进程?) [英] stdout and stderr Listener issues (causing frozen processes?)

查看:99
本文介绍了stdout和stderr监听器问题(导致冻结进程?)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述





我继承了一个应用程序,通过调用我的应用程序然后监听的处理来调用其他应用程序(robocopy,rsh等...)记录输出。



用于执行监听的方法被搁置(并且非常滑稽)并导致进程冻结。没有办法释放它们,除非我杀死导致整个管道失败的过程,这导致令人生气的电子邮件从烦恼的利益相关者发送给我。



我通过使用stdout和stderr事件而不是流读取器改变了处理过程的方式。 99%的情况下工作正常。



我看的1%看起来很奇怪:我有DebugViewer运行崩溃(忘了限制深度)和我现在有3个robocopys(robocopies?)无法继续。我杀了DebugView并且robocopys继续了几秒钟。我只能认为它们一直运行直到输出缓冲区填满,现在它们再次被冻结。



我已重新启动DebugView(此次设置了深度)但是Robocopys仍然被冻结。



是不是因为我的监听器事件本身没有被监听,输出缓冲区实际上并没有被读取?我的印象是这些事件是从缓冲区中读取的,无论它们本身是否附加了侦听器。



我的问题是两部分:这是怎么发生的,如何防止它?



如果您需要更多详细信息,请告诉我



提前致谢^ _ ^



以下是监听输出的基本Command.Execute代码:



Hi,

I have inherited an application that calls other apps (robocopy, rsh, etc...) by invoking processed that my application then listens to and logs the outputs.

The method that was in use to perform the listening was floored (and quite comical) and caused processes to freeze. There was no way to free them unless I kill the process which causes an entire pipeline to fail which results in nasty email being sent to me from annoyed stake-holders.

I changed the way the the processed were listened to by using stdout and stderr events instead of stream readers. This works fine 99% of the time.

The 1% I am looking at seem odd: I had DebugViewer running which crashed (forgot to limit the depth) and I now have 3 robocopys (robocopies?) that can't continue. I killed DebugView and the robocopys continue, for a few seconds. I can only think that they ran until the output buffers filled up, and now they are frozen again.

I have restarted DebugView (with depth set this time) but the Robocopys are still frozen.

Is it the case that, because my listener events aren't themselves listened to, the output buffers are not actually being read from? My impression was that these events read from the buffers, whether they themselves have listeners attached or not.

My question is two part: How does this happen and how can I prevent it?

Please let me know if you would like any more detail

Thanks in advance ^_^

Here is the base Command.Execute code that listens to the outputs:

public virtual int Execute()
{
    // execute the process
    ProcessStartInfo processInfo = new ProcessStartInfo(ExecutableName, Options);
    Log.WriteLineTrack("Command.Execute: " + ExecutableName + " " + Options);

    OnNewEvent("Command.Execute: " + ExecutableName + " " + Options, 0, "");

    // Notes:
    // 1. I think CreateNoWindow has to be true to prevent the launched cmd file truing to access a desktop (and failing)
    // 2. the credentials must have access to all drives required to fulfill the supplied command line

    processInfo.RedirectStandardOutput = true;
    processInfo.RedirectStandardError = true;
    processInfo.CreateNoWindow = true;
    processInfo.UseShellExecute = false;
    processInfo.WorkingDirectory = WorkingDirectory;

    Process rcpProcess = new Process { StartInfo = processInfo };

    //StringBuilder strOut = new StringBuilder();
    StringBuilder strErr = new StringBuilder();

    string exeName = Path.GetFileNameWithoutExtension(ExecutableName);

    _processId = 0;
    rcpProcess.OutputDataReceived += delegate(object sender, DataReceivedEventArgs args)
                                         {
                                             if (!string.IsNullOrEmpty(args.Data))
                                             {
                                                 //strOut.AppendLine(args.Data);
                                                 OnStdOut(args.Data, _processId, exeName);
                                             }
                                         };
    rcpProcess.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs args)
                                        {
                                            if (!string.IsNullOrEmpty(args.Data))
                                            {
                                                if (args.Data.Substring(0, 2).ToUpper().Equals("@."))
                                                {
                                                    OnNewEvent(args.Data, _processId, exeName);
                                                }
                                                else
                                                {
                                                    strErr.AppendLine(args.Data);
                                                    OnStdErr(args.Data, _processId, exeName);
                                                }
                                            }
                                        };
    rcpProcess.Start();

    _processId = rcpProcess.Id;

    rcpProcess.BeginOutputReadLine();
    rcpProcess.BeginErrorReadLine();

    if (_timeout != 0)
    {
        rcpProcess.WaitForExit(_timeout);
    }
    else
    {
        rcpProcess.WaitForExit(); //can take a millisecs tiumeout arg
    }

    int rc = rcpProcess.ExitCode;

    StringBuilder strbOutput = new StringBuilder();
    if (_logAgainstRun)
    {
        if (strErr.Length > 0)
        {
            strbOutput.AppendLine(string.Format("Executable {0} returned errors", ExecutableName));
            strbOutput.Append("<stdout>");
            strbOutput.Append(strErr);
            strbOutput.AppendLine("</stdout>");
            strbOutput.AppendLine(string.Format("Return code: {0}", rc));
        }
        else
        {
            strbOutput.AppendLine(string.Format("Executable {0} completed", ExecutableName));
            strbOutput.AppendLine(string.Format("Return code: {0}", rc));
        }
        Log.LogForRunOnly(_runId, _logfileName, strbOutput.ToString());
    }

    rcpProcess.Dispose();
    return rc;
}





PS:如果父流程崩溃也会引发问题。我可以在崩溃之前释放输出的重定向吗?



PS: what happens if the parent process crashes is also a concern. Can I release the redirection of the outputs before crash?

推荐答案

我在一个小程序中看到了类似的东西,我创建了一个小程序来运行SysInternals sdelete.exe和收集他们的标准输出。



我会解释我的情况,以便你可以确定它是否与你的相关。



每个流程实例是在一个单独的线程中创建的,代码的相关部分是相当标准的东西:

I've seen something similar in a small program I created to run several instances of SysInternals sdelete.exe and collect their standard output.

I'll explain my situation so that you can determine if it's relevant to yours.

Each Process instance was created in a separate thread and the relevant part of the code was this fairly standard stuff:
Process p = new Process();
p.StartInfo = psi;
p.Start();
String result = p.StandardOutput.ReadToEnd();
p.WaitForExit();





偶尔,TaskManager会显示子进程已经结束,但相应的ReadToEnd会阻塞,直到其中一个进程运行更长时间子进程结束了。有些奇怪的事情显然是不可能发生的!我可以证明问题与sdelete.exe无关,并得出Process.Start方法不是线程安全的结论。特别是如果在不同线程上执行Start方法重叠,则进程的重定向输出流之间将存在耦合。



解决方案只是为了防止重叠调用Process.Start与所有线程之间共享的锁对象。通过这种修改,流程不再表现出奇怪的耦合行为。



Occasionally TaskManager would show that a child process had ended but the corresponding ReadToEnd would block until one of the other longer running child processes ended. Something odd was going on as obviously that could not happen! I could show that the problem was nothing to do with sdelete.exe and came to the conclusion that the Process.Start method is not thread safe. Specifically if the execution of the Start method on different threads overlaps there will be a coupling between the processes' redirected output streams.

The solution was simply to prevent overlapping calls to Process.Start with lock object shared between all the threads. With this modification in place the processes no longer exhibited the strange coupling behaviour.

Process p = new Process();
p.StartInfo = psi;
lock (startLock) {
  p.Start();
}
String result = p.StandardOutput.ReadToEnd();
p.WaitForExit();





如果有任何帮助,请告诉我。



关于你的代码的一些其他评论,不是你问的,但知道的东西很有用。



输出流可能会在WaitForExit返回后传递数据,使其不安全假设您拥有该点的所有数据。它还使得很难知道何时调用Process.Dispose方法。解决方案是在两个输出流都返回null之前不进行,指示它们已经关闭,即 DataReceivedEventArgs.Data == null



顺便提一下,您的DataReceived事件处理程序正在丢弃空白行。这可能是你想要的,但要注意 args.Data == String.Empty 是完全正常的,意味着空行被写入输出流。另一方面, args.Data == null 仅在流关闭时发生一次。





Alan。



Let me know if that's of any help.

Some other comments on your code, not that you asked, but it's useful to know stuff.

The output streams may deliver data after WaitForExit has returned making it unsafe to assume that you have all the data at that point. It also makes it very difficult to know when to call the Process.Dispose method. The solution is not to proceed until both output streams have returned null, the indicator that they have closed, i.e. DataReceivedEventArgs.Data == null.

Incidentally your DataReceived event handlers are discarding blank lines. This may be what you want but do be aware that args.Data == String.Empty is perfectly normal and means a blank line was written to the output stream. On the other hand args.Data == null occurs once only when the stream has closed.


Alan.


这篇关于stdout和stderr监听器问题(导致冻结进程?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆