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
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 StandardOutput
and/or StandardError
the 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
StandardOutput
the process can block trying to write to it, so the process never ends. - If you read from
StandardOutput
using ReadToEnd then yourprocess can block if the process never closesStandardOutput
(for example if it never terminates, or if it is blocked writing toStandardError
).
- 如果在读取
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 StandardOutput
and StandardError
you can do this:
解决方案是使用异步读取来确保缓冲区不会变满。为了避免任何死锁并收集两者的所有输出StandardOutput
,StandardError
您可以这样做:
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.StandardOutput
says 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 OutputDataReceived
and ErrorDataReceived
delegates need to be removed before the outputWaitHandle
and errorWaitHandle
get disposed. If the process continues to output data after the timeout has been exceeded and then terminates, the outputWaitHandle
and errorWaitHandle
variables will be accessed after being disposed.
在OutputDataReceived
和ErrorDataReceived
代表们需要之前被删除outputWaitHandle
,并errorWaitHandle
得到安置。如果进程在超时后继续输出数据然后终止,则outputWaitHandle
和errorWaitHandle
变量将在被处理后访问。
(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();