C# ProcessStartInfo 挂在“WaitForExit”上?为什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/139593/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 15:10:51  来源:igfitidea点击:

ProcessStartInfo hanging on "WaitForExit"? Why?

提问by Epaga

I have the following code:

我有以下代码:

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args));
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

I know that the output from the process I am starting is around 7MB long. Running it in the Windows console works fine. Unfortunately programmatically this hangs indefinitely at WaitForExit. Note also this does code NOT hang for smaller outputs (like 3KB).

我知道我开始的过程的输出大约有 7MB 长。在 Windows 控制台中运行它可以正常工作。不幸的是,这在WaitForExit 处以编程方式无限期挂起。另请注意,对于较小的输出(如 3KB),这不会挂起代码。

Is it possible that the internal StandardOutput in ProcessStartInfo can't buffer 7MB? If so, what should I do instead? If not, what am I doing wrong?

ProcessStartInfo 中的内部 StandardOutput 是否可能无法缓冲 7MB?如果是这样,我应该怎么做?如果没有,我做错了什么?

采纳答案by Mark Byers

The problem is that if you redirect StandardOutputand/or StandardErrorthe internal buffer can become full. Whatever order you use, there can be a problem:

问题是,如果您重定向StandardOutput和/或StandardError内部缓冲区可能已满。无论您使用何种顺序,都可能存在问题:

  • If you wait for the process to exit before reading StandardOutputthe process can block trying to write to it, so the process never ends.
  • If you read from StandardOutputusing ReadToEnd then yourprocess can block if the process never closes StandardOutput(for example if it never terminates, or if it is blocked writing to StandardError).
  • 如果在读取StandardOutput进程之前等待进程退出可能会阻止尝试写入它,因此进程永远不会结束。
  • 如果您StandardOutput使用 ReadToEnd 进行读取,那么如果进程从未关闭(例如,如果它从未终止,或者它被阻止写入),则您的进程可能StandardOutput会阻塞StandardError

The solution is to use asynchronous reads to ensure that the buffer doesn't get full. To avoid any deadlocks and collect up all output from both StandardOutputand StandardErroryou can do this:

解决方案是使用异步读取来确保缓冲区不会变满。为了避免任何死锁并收集两者的所有输出StandardOutputStandardError您可以这样做:

EDIT: See answers below for how avoid an ObjectDisposedExceptionif the timeout occurs.

编辑:如果发生超时,请参阅下面的答案以了解如何避免ObjectDisposedException

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
    using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
    {
        process.OutputDataReceived += (sender, e) => {
            if (e.Data == null)
            {
                outputWaitHandle.Set();
            }
            else
            {
                output.AppendLine(e.Data);
            }
        };
        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data == null)
            {
                errorWaitHandle.Set();
            }
            else
            {
                error.AppendLine(e.Data);
            }
        };

        process.Start();

        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        if (process.WaitForExit(timeout) &&
            outputWaitHandle.WaitOne(timeout) &&
            errorWaitHandle.WaitOne(timeout))
        {
            // Process completed. Check process.ExitCode here.
        }
        else
        {
            // Timed out.
        }
    }
}

回答by Rob

The documentationfor Process.StandardOutputsays to read before you wait otherwise you can deadlock, snippet copied below:

文档Process.StandardOutput说读取等待时间,否则你可以死锁之前,片段复制如下:

 // Start the child process.
 Process p = new Process();
 // Redirect the output stream of the child process.
 p.StartInfo.UseShellExecute = false;
 p.StartInfo.RedirectStandardOutput = true;
 p.StartInfo.FileName = "Write500Lines.exe";
 p.Start();
 // Do not wait for the child process to exit before
 // reading to the end of its redirected stream.
 // p.WaitForExit();
 // Read the output stream first and then wait.
 string output = p.StandardOutput.ReadToEnd();
 p.WaitForExit();

回答by torial

We have this issue as well (or a variant).

我们也有这个问题(或变体)。

Try the following:

请尝试以下操作:

1) Add a timeout to p.WaitForExit(nnnn); where nnnn is in milliseconds.

1) 给 p.WaitForExit(nnnn) 添加一个超时时间;其中 nnnn 以毫秒为单位。

2) Put the ReadToEnd call before the WaitForExit call. This iswhat we've seen MS recommend.

2) 将 ReadToEnd 调用置于 WaitForExit 调用之前。这我们看到的 MS 推荐的。

回答by song

This post maybe outdated but i found out the main cause why it usually hang is due to stack overflow for the redirectStandardoutput or if you have redirectStandarderror.

这篇文章可能已经过时,但我发现它通常挂起的主要原因是由于 redirectStandardoutput 的堆栈溢出或者是否有 redirectStandarderror。

As the output data or the error data is large, it will cause a hang time as it is still processing for indefinite duration.

由于输出数据或错误数据较大,会导致挂起时间,因为它仍在无限期地处理。

so to resolve this issue:

所以要解决这个问题:

p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False

回答by stevejay

Mark Byers' answer is excellent, but I would just add the following:

Mark Byers 的回答非常好,但我只想添加以下内容:

The OutputDataReceivedand ErrorDataReceiveddelegates need to be removed before the outputWaitHandleand errorWaitHandleget disposed. If the process continues to output data after the timeout has been exceeded and then terminates, the outputWaitHandleand errorWaitHandlevariables will be accessed after being disposed.

OutputDataReceivedErrorDataReceived代表们需要之前被删除outputWaitHandle,并errorWaitHandle得到安置。如果进程在超时后继续输出数据然后终止,则outputWaitHandleerrorWaitHandle变量将在被处理后访问。

(FYI I had to add this caveat as an answer as I couldn't comment on his post.)

(仅供参考,我不得不添加这个警告作为答案,因为我无法对他的帖子发表评论。)

回答by Kuzman Marinov

I thing that this is simple and better approach (we don't need AutoResetEvent):

我认为这是简单且更好的方法(我们不需要AutoResetEvent):

public static string GGSCIShell(string Path, string Command)
{
    using (Process process = new Process())
    {
        process.StartInfo.WorkingDirectory = Path;
        process.StartInfo.FileName = Path + @"\ggsci.exe";
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.UseShellExecute = false;

        StringBuilder output = new StringBuilder();
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.Start();
        process.StandardInput.WriteLine(Command);
        process.BeginOutputReadLine();


        int timeoutParts = 10;
        int timeoutPart = (int)TIMEOUT / timeoutParts;
        do
        {
            Thread.Sleep(500);//sometimes halv scond is enough to empty output buff (therefore "exit" will be accepted without "timeoutPart" waiting)
            process.StandardInput.WriteLine("exit");
            timeoutParts--;
        }
        while (!process.WaitForExit(timeoutPart) && timeoutParts > 0);

        if (timeoutParts <= 0)
        {
            output.AppendLine("------ GGSCIShell TIMEOUT: " + TIMEOUT + "ms ------");
        }

        string result = output.ToString();
        return result;
    }
}

回答by Karol Tyl

The problem with unhandled ObjectDisposedException happens when the process is timed out. In such case the other parts of the condition:

未处理的 ObjectDisposedException 问题发生在进程超时时。在这种情况下,条件的其他部分:

if (process.WaitForExit(timeout) 
    && outputWaitHandle.WaitOne(timeout) 
    && errorWaitHandle.WaitOne(timeout))

are not executed. I resolved this problem in a following way:

不被执行。我通过以下方式解决了这个问题:

using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
    using (Process process = new Process())
    {
        // preparing ProcessStartInfo

        try
        {
            process.OutputDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        outputWaitHandle.Set();
                    }
                    else
                    {
                        outputBuilder.AppendLine(e.Data);
                    }
                };
            process.ErrorDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        errorWaitHandle.Set();
                    }
                    else
                    {
                        errorBuilder.AppendLine(e.Data);
                    }
                };

            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            if (process.WaitForExit(timeout))
            {
                exitCode = process.ExitCode;
            }
            else
            {
                // timed out
            }

            output = outputBuilder.ToString();
        }
        finally
        {
            outputWaitHandle.WaitOne(timeout);
            errorWaitHandle.WaitOne(timeout);
        }
    }
}

回答by ohgodnotanotherone

I was having the same issue, but the reason was different. It would however happen under Windows 8, but not under Windows 7. The following line seems to have caused the problem.

我遇到了同样的问题,但原因不同。然而,它会在 Windows 8 下发生,但不会在 Windows 7 下发生。以下行似乎导致了问题。

pProcess.StartInfo.UseShellExecute = False

The solution was to NOT disable UseShellExecute. I now received a Shell popup window, which is unwanted, but much better than the program waiting for nothing particular to happen. So I added the following work-around for that:

解决方案是不禁用 UseShellExecute。我现在收到了一个 Shell 弹出窗口,这是不需要的,但比等待任何特殊事件发生的程序要好得多。所以我为此添加了以下解决方法:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

Now the only thing bothering me is to why this is happening under Windows 8 in the first place.

现在唯一困扰我的是为什么这首先会在 Windows 8 下发生。

回答by Elina Maliarsky

I solved it this way:

我是这样解决的:

            Process proc = new Process();
            proc.StartInfo.FileName = batchFile;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.RedirectStandardError = true;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;      
            proc.Start();
            StreamWriter streamWriter = proc.StandardInput;
            StreamReader outputReader = proc.StandardOutput;
            StreamReader errorReader = proc.StandardError;
            while (!outputReader.EndOfStream)
            {
                string text = outputReader.ReadLine();                    
                streamWriter.WriteLine(text);
            }

            while (!errorReader.EndOfStream)
            {                   
                string text = errorReader.ReadLine();
                streamWriter.WriteLine(text);
            }

            streamWriter.Close();
            proc.WaitForExit();

I redirected both input, output and error and handled reading from output and error streams. This solution works for SDK 7- 8.1, both for Windows 7 and Windows 8

我重定向了输入、输出和错误,并处理了从输出和错误流中的读取。此解决方案适用于 SDK 7-8.1,适用于 Windows 7 和 Windows 8

回答by Jon

Rob answered it and saved me few more hours of trials. Read the output/error buffer before waiting:

Rob 回答了这个问题,并为我节省了几个小时的试验时间。在等待之前读取输出/错误缓冲区:

// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();