我有以下代码:
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
进程之前等待进程退出,则可以阻止尝试写入进程,因此进程永远不会结束.
如果您从StandardOutput
使用ReadToEnd 读取,那么您的进程可以阻止进程永远不会关闭StandardOutput
(例如,如果它永远不会终止,或者它被阻止写入StandardError
).
解决方案是使用异步读取来确保缓冲区不会满.要避免任何死锁并收集两者的所有输出StandardOutput
,StandardError
您可以这样做:
编辑:请参阅下面的答案,了解如何在发生超时时避免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. } } }
在您等待之前说要阅读的文档,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();
Mark Byers的回答非常好,但我只想添加以下内容:在outputWaitHandle和errorWaitHandle被释放之前,需要删除OutputDataReceived和ErrorDataReceived委托.如果进程在超出超时后继续输出数据然后终止,则在处理后将访问outputWaitHandle和errorWaitHandle变量.
(仅供参考我不得不加上这个警告作为答案,因为我无法对他的帖子发表评论.)
当进程超时时,会发生未处理的ObjectDisposedException问题.在这种情况下,条件的其他部分:
if (process.WaitForExit(timeout) && outputWaitHandle.WaitOne(timeout) && errorWaitHandle.WaitOne(timeout))
没有执行.我通过以下方式解决了这个问题:
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); } } }
这是一个更现代的等待,基于任务并行库(TPL)的.NET 4.5及更高版本的解决方案.
用法示例try { var exitCode = await StartProcess( "dotnet", "--version", @"C:\", 10000, Console.Out, Console.Out); Console.WriteLine($"Process Exited with Exit Code {exitCode}!"); } catch (TaskCanceledException) { Console.WriteLine("Process Timed Out!"); }履行
public static async TaskStartProcess( string filename, string arguments, string workingDirectory= null, int? timeout = null, TextWriter outputTextWriter = null, TextWriter errorTextWriter = null) { using (var process = new Process() { StartInfo = new ProcessStartInfo() { CreateNoWindow = true, Arguments = arguments, FileName = filename, RedirectStandardOutput = outputTextWriter != null, RedirectStandardError = errorTextWriter != null, UseShellExecute = false, WorkingDirectory = workingDirectory } }) { var cancellationTokenSource = timeout.HasValue ? new CancellationTokenSource(timeout.Value) : new CancellationTokenSource(); process.Start(); var tasks = new List (3) { process.WaitForExitAsync(cancellationTokenSource.Token) }; if (outputTextWriter != null) { tasks.Add(ReadAsync( x => { process.OutputDataReceived += x; process.BeginOutputReadLine(); }, x => process.OutputDataReceived -= x, outputTextWriter, cancellationTokenSource.Token)); } if (errorTextWriter != null) { tasks.Add(ReadAsync( x => { process.ErrorDataReceived += x; process.BeginErrorReadLine(); }, x => process.ErrorDataReceived -= x, errorTextWriter, cancellationTokenSource.Token)); } await Task.WhenAll(tasks); return process.ExitCode; } } /// /// Waits asynchronously for the process to exit. /// /// The process to wait for cancellation. /// A cancellation token. If invoked, the task will return /// immediately as cancelled. ///A Task representing waiting for the process to end. public static Task WaitForExitAsync( this Process process, CancellationToken cancellationToken = default(CancellationToken)) { process.EnableRaisingEvents = true; var taskCompletionSource = new TaskCompletionSource
罗布回答了这个问题并为我节省了几个小时的试验.在等待之前读取输出/错误缓冲区:
// Read the output stream first and then wait. string output = p.StandardOutput.ReadToEnd(); p.WaitForExit();
我们也有这个问题(或变体).
请尝试以下方法:
1)向p.WaitForExit(nnnn)添加超时; 其中nnnn以毫秒为单位.
2)在WaitForExit调用之前放入ReadToEnd调用.这就是我们所见过的MS推荐.