我有以下代码:
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?如果是,我该怎么办?如果不是,我做错了什么?
https://stackoverflow.com/a/17600012/4151626的信用归于EM0
我的应用程序的其他解决方案(包括EM0)仍然处于死锁状态,这是由于内部超时以及派生应用程序同时使用StandardOutput和standderror。以下是对我有效的方法:
Process p = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = exe,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
p.Start();
string cv_error = null;
Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
et.Start();
string cv_out = null;
Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
ot.Start();
p.WaitForExit();
ot.Join();
et.Join();
编辑:添加初始化StartInfo代码示例
https://stackoverflow.com/a/17600012/4151626的信用归于EM0
我的应用程序的其他解决方案(包括EM0)仍然处于死锁状态,这是由于内部超时以及派生应用程序同时使用StandardOutput和standderror。以下是对我有效的方法:
Process p = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = exe,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
p.Start();
string cv_error = null;
Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
et.Start();
string cv_out = null;
Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
ot.Start();
p.WaitForExit();
ot.Join();
et.Join();
编辑:添加初始化StartInfo代码示例
未处理的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);
}
}
}
问题是,如果你重定向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的回答和Karol Tyl的回答,我根据我想如何使用流程编写了完整的代码。Start方法。
使用
我用它来创建git命令的进度对话框。我是这样使用它的:
private bool Run(string fullCommand)
{
Error = "";
int timeout = 5000;
var result = ProcessNoBS.Start(
filename: @"C:\Program Files\Git\cmd\git.exe",
arguments: fullCommand,
timeoutInMs: timeout,
workingDir: @"C:\test");
if (result.hasTimedOut)
{
Error = String.Format("Timeout ({0} sec)", timeout/1000);
return false;
}
if (result.ExitCode != 0)
{
Error = (String.IsNullOrWhiteSpace(result.stderr))
? result.stdout : result.stderr;
return false;
}
return true;
}
理论上,您还可以结合stdout和stderr,但我还没有对此进行测试。
Code
public struct ProcessResult
{
public string stdout;
public string stderr;
public bool hasTimedOut;
private int? exitCode;
public ProcessResult(bool hasTimedOut = true)
{
this.hasTimedOut = hasTimedOut;
stdout = null;
stderr = null;
exitCode = null;
}
public int ExitCode
{
get
{
if (hasTimedOut)
throw new InvalidOperationException(
"There was no exit code - process has timed out.");
return (int)exitCode;
}
set
{
exitCode = value;
}
}
}
public class ProcessNoBS
{
public static ProcessResult Start(string filename, string arguments,
string workingDir = null, int timeoutInMs = 5000,
bool combineStdoutAndStderr = false)
{
using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
using (var process = new Process())
{
var info = new ProcessStartInfo();
info.CreateNoWindow = true;
info.FileName = filename;
info.Arguments = arguments;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
if (workingDir != null)
info.WorkingDirectory = workingDir;
process.StartInfo = info;
StringBuilder stdout = new StringBuilder();
StringBuilder stderr = combineStdoutAndStderr
? stdout : new StringBuilder();
var result = new ProcessResult();
try
{
process.OutputDataReceived += (sender, e) =>
{
if (e.Data == null)
outputWaitHandle.Set();
else
stdout.AppendLine(e.Data);
};
process.ErrorDataReceived += (sender, e) =>
{
if (e.Data == null)
errorWaitHandle.Set();
else
stderr.AppendLine(e.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
if (process.WaitForExit(timeoutInMs))
result.ExitCode = process.ExitCode;
// else process has timed out
// but that's already default ProcessResult
result.stdout = stdout.ToString();
if (combineStdoutAndStderr)
result.stderr = null;
else
result.stderr = stderr.ToString();
return result;
}
finally
{
outputWaitHandle.WaitOne(timeoutInMs);
errorWaitHandle.WaitOne(timeoutInMs);
}
}
}
}
}