我有以下代码:
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
我知道我正在启动的进程的输出大约有7MB长。在Windows控制台中运行它可以正常工作。不幸的是,从编程的角度来看,它会无限期地挂在WaitForExit上。还要注意,对于较小的输出(比如3KB),这段代码不会挂起。
ProcessStartInfo中的内部StandardOutput是否可能不能缓冲7MB?如果是,我该怎么办?如果不是,我做错了什么?
问题是,如果你重定向StandardOutput和/或StandardError,内部缓冲区可能会满。不管你用什么顺序,都会有一个问题:
如果在读取StandardOutput之前等待进程退出,进程可能会阻塞尝试写入它,因此进程永远不会结束。
如果你使用ReadToEnd从StandardOutput中读取,那么如果进程从未关闭StandardOutput(例如,如果它从未终止,或者如果它被阻塞写入到standderror),那么你的进程就会阻塞。
解决方案是使用异步读取来确保缓冲区不会被填满。为了避免死锁并从StandardOutput和standderror收集所有输出,你可以这样做:
编辑:请参阅下面的回答,了解如何在超时发生时避免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.
}
}
}
我试图通过考虑Mark Byers, Rob, stevejay的回答,创建一个类来解决你使用异步流读取的问题。这样做,我意识到有一个与异步流程输出流读取相关的错误。
我在微软报告了这个漏洞:https://connect.microsoft.com/VisualStudio/feedback/details/3119134
简介:
You can't do that:
process.BeginOutputReadLine(); process.Start();
You will receive System.InvalidOperationException : StandardOut has
not been redirected or the process hasn't started yet.
============================================================================================================================
Then you have to start asynchronous output read after the process is
started:
process.Start(); process.BeginOutputReadLine();
Doing so, make a race condition because the output stream can receive
data before you set it to asynchronous:
process.Start();
// Here the operating system could give the cpu to another thread.
// For example, the newly created thread (Process) and it could start writing to the output
// immediately before next line would execute.
// That create a race condition.
process.BeginOutputReadLine();
============================================================================================================================
Then some people could say that you just have to read the stream
before you set it to asynchronous. But the same problem occurs. There
will be a race condition between the synchronous read and set the
stream into asynchronous mode.
============================================================================================================================
There is no way to acheive safe asynchronous read of an output stream
of a process in the actual way "Process" and "ProcessStartInfo" has
been designed.
对于您的情况,您最好使用其他用户建议的异步读取。但是您应该意识到,由于竞态条件,您可能会错过一些信息。
让我们将这里发布的示例代码称为重定向程序,将另一个程序称为重定向程序。如果是我,我可能会写一个测试重定向程序,可以用来复制这个问题。
于是我照做了。对于测试数据,我使用ECMA-334 c#语言规范v PDF;大约5MB。下面是其中的重要部分。
StreamReader stream = null;
try { stream = new StreamReader(Path); }
catch (Exception ex)
{
Console.Error.WriteLine("Input open error: " + ex.Message);
return;
}
Console.SetIn(stream);
int datasize = 0;
try
{
string record = Console.ReadLine();
while (record != null)
{
datasize += record.Length + 2;
record = Console.ReadLine();
Console.WriteLine(record);
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
return;
}
datasize值与实际文件大小不匹配,但这无关紧要。目前还不清楚PDF文件是否总是在行尾同时使用CR和LF,但这并不重要。您可以使用任何其他大型文本文件进行测试。
使用这种方法,当我写入大量数据时,示例重定向器代码挂起,而当我写入少量数据时则不会挂起。
我试图以某种方式跟踪该代码的执行,但我做不到。我注释掉了重定向程序中禁用为重定向程序创建控制台的行,以尝试获得单独的控制台窗口,但我不能。
然后我发现了如何在新窗口、父窗口或没有窗口中启动控制台应用程序。所以很明显,当一个控制台程序在没有ShellExecute的情况下启动另一个控制台程序时,我们不能(很容易)拥有一个单独的控制台,因为ShellExecute不支持重定向,我们必须共享一个控制台,即使我们没有为其他进程指定窗口。
我假设,如果重定向程序在某个地方填满了缓冲区,那么它必须等待数据被读取,如果此时重定向程序没有读取数据,那么它就是死锁。
解决方案是不使用ReadToEnd,而是在写入数据时读取数据,但没有必要使用异步读取。解决方法很简单。下面是5 MB的PDF文件。
ProcessStartInfo info = new ProcessStartInfo(TheProgram);
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
string record = p.StandardOutput.ReadLine();
while (record != null)
{
Console.WriteLine(record);
record = p.StandardOutput.ReadLine();
}
p.WaitForExit();
另一种可能是使用GUI程序进行重定向。上面的代码在WPF应用程序中工作,除非进行了明显的修改。
在阅读了这里所有的帖子之后,我确定了Marko avlijaovic的统一解决方案。
然而,它并没有解决我所有的问题。
在我们的环境中,我们有一个Windows服务,计划运行数百个不同的.bat .cmd .exe,…等等,这些文件积累了多年,由许多不同的人以不同的风格编写。我们无法控制程序和脚本的编写,我们只负责调度、运行和成功/失败的报告。
所以我尝试了这里所有的建议,并取得了不同程度的成功。Marko的回答几乎是完美的,但是当作为服务运行时,它并不总是捕获标准输出。我一直没搞清楚为什么不行。
我们发现在所有情况下都有效的唯一解决方案是:http://csharptest.net/319/using-the-processrunner-class/index.html