ProcessStartInfo 挂在“WaitForExit”上?为啥?
Posted
技术标签:
【中文标题】ProcessStartInfo 挂在“WaitForExit”上?为啥?【英文标题】:ProcessStartInfo hanging on "WaitForExit"? Why?ProcessStartInfo 挂在“WaitForExit”上?为什么? 【发布时间】:2010-09-13 11:12:12 【问题描述】:我有以下代码:
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?如果是这样,我应该怎么做?如果没有,我做错了什么?
【问题讨论】:
任何带有完整源代码的最终解决方案? 我遇到了同样的问题,这就是我解决它的方法***.com/questions/2285288/… 是的,最终解决方案:交换最后两行。它在manual。 来自 msdn:代码示例通过在 p.WaitForExit 之前调用 p.StandardOutput.ReadToEnd 来避免死锁情况。如果父进程在 p.StandardOutput.ReadToEnd 之前调用 p.WaitForExit 并且子进程写入足够的文本来填充重定向的流,则可能导致死锁情况。父进程将无限期地等待子进程退出。子进程将无限期地等待父进程从完整的 StandardOutput 流中读取。 正确执行此操作有多复杂,这有点烦人。很高兴通过更简单的命令行重定向 > 输出文件来解决它 :) 【参考方案1】:问题是,如果您重定向StandardOutput
和/或StandardError
,内部缓冲区可能会变满。无论您使用什么顺序,都可能出现问题:
StandardOutput
之前等待进程退出,该进程可能会阻止尝试写入它,因此该进程永远不会结束。
如果您使用 ReadToEnd 从StandardOutput
读取,那么如果进程从不关闭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.
【讨论】:
不知道重定向输出会导致问题,但确实如此。花了 4 个小时来解决这个问题,并在阅读您的帖子后 5 分钟内将其修复。干得好! @AlexPeck 问题是将其作为控制台应用程序运行。 Hans Passant 在这里发现了这个问题:***.com/a/16218470/279516 每次命令提示符关闭时,都会出现:在 mscorlib.dll 中发生“System.ObjectDisposed”类型的未处理异常附加信息:安全句柄已关闭 我们遇到了与上面@user1663380 所述类似的问题。您是否认为事件处理程序的using
语句需要高于进程本身的using
语句?
我认为不需要等待句柄。根据 msdn,只需使用 WaitForExit 的非超时版本即可:当标准输出已被重定向到异步事件处理程序时,当此方法返回时,输出处理可能尚未完成。为确保异步事件处理已完成,请在从该重载接收到 true 后调用不带参数的 WaitForExit() 重载。【参考方案2】:
documentation 的 Process.StandardOutput
表示要在等待之前阅读,否则会出现死锁,sn-p 复制如下:
// 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();
【讨论】:
我不能 100% 确定这是否只是我的环境的结果,但我发现如果你设置了RedirectStandardOutput = true;
并且不使用p.StandardOutput.ReadToEnd();
,你会遇到死锁/挂起。
是的。我也有类似的情况。在进程中使用 ffmpeg 进行转换时,我无缘无故地重定向了 StandardError,它在 StandardError 流中写入的内容足以造成死锁。
即使重定向和读取标准输出,这对我来说仍然挂起。
@user3791372 我想这仅适用于 StandardOutput 后面的缓冲区未完全填充的情况。在这里,MSDN 不公平。我建议您阅读的一篇很棒的文章位于:dzone.com/articles/async-io-and-threadpool【参考方案3】:
这是一个更现代的等待,基于任务并行库 (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 Task<int> StartProcess(
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<Task>(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;
/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as cancelled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(
this Process process,
CancellationToken cancellationToken = default(CancellationToken))
process.EnableRaisingEvents = true;
var taskCompletionSource = new TaskCompletionSource<object>();
EventHandler handler = null;
handler = (sender, args) =>
process.Exited -= handler;
taskCompletionSource.TrySetResult(null);
;
process.Exited += handler;
if (cancellationToken != default(CancellationToken))
cancellationToken.Register(
() =>
process.Exited -= handler;
taskCompletionSource.TrySetCanceled();
);
return taskCompletionSource.Task;
/// <summary>
/// Reads the data from the specified data recieved event and writes it to the
/// <paramref name="textWriter"/>.
/// </summary>
/// <param name="addHandler">Adds the event handler.</param>
/// <param name="removeHandler">Removes the event handler.</param>
/// <param name="textWriter">The text writer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task ReadAsync(
this Action<DataReceivedEventHandler> addHandler,
Action<DataReceivedEventHandler> removeHandler,
TextWriter textWriter,
CancellationToken cancellationToken = default(CancellationToken))
var taskCompletionSource = new TaskCompletionSource<object>();
DataReceivedEventHandler handler = null;
handler = new DataReceivedEventHandler(
(sender, e) =>
if (e.Data == null)
removeHandler(handler);
taskCompletionSource.TrySetResult(null);
else
textWriter.WriteLine(e.Data);
);
addHandler(handler);
if (cancellationToken != default(CancellationToken))
cancellationToken.Register(
() =>
removeHandler(handler);
taskCompletionSource.TrySetCanceled();
);
return taskCompletionSource.Task;
【讨论】:
迄今为止最好最完整的答案 出于某种原因,这是唯一对我有用的解决方案,应用程序停止挂起。 看来,您没有处理这种情况,即进程在启动后结束,但在附加 Exited 事件之前结束。我的建议 - 在所有注册后开始该过程。 @StasBoyarincev 谢谢,已更新。我忘记使用此更改更新 *** 答案。 @MuhammadRehanSaeed 还有一件事 - 在 process.Start 之前似乎不允许调用 process.BeginOutputReadLine() 或 process.BeginErrorReadLine()。在这种情况下,我收到错误:StandardOut 尚未重定向或进程尚未开始。【参考方案4】:Mark Byers 的回答非常好,但我只想添加以下内容:
OutputDataReceived
和 ErrorDataReceived
代表需要在 outputWaitHandle
和 errorWaitHandle
被释放之前被删除。如果进程超过超时后继续输出数据然后终止,则outputWaitHandle
和errorWaitHandle
变量在被释放后会被访问。
(仅供参考,我不得不添加这个警告作为答案,因为我无法评论他的帖子。)
【讨论】:
或许打电话给CancelOutputRead会更好? 将马克的编辑代码添加到这个答案中会非常棒!我现在遇到了完全相同的问题。 @ianbailey 解决此问题的最简单方法是将 using(Process p ...) 放入 using(AutoResetEvent errorWaitHandle...)【参考方案5】:当进程超时时,会出现未处理的 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);
【讨论】:
为了完整起见,这里缺少将重定向设置为 true 并且我已经删除了最后的超时,因为该过程可能会要求用户输入(例如输入内容)所以我不想要求用户快速 为什么将output
和error
更改为outputBuilder
?有人可以提供有效的完整答案吗?
System.ObjectDisposedException: 安全句柄已被关闭也发生在这个版本上【参考方案6】:
Rob 回答了它并为我节省了几个小时的试验时间。在等待之前读取输出/错误缓冲区:
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
【讨论】:
但是如果在您致电WaitForExit()
后有更多数据出现怎么办?
@knocte 根据我的测试,ReadToEnd
或类似方法(如StandardOutput.BaseStream.CopyTo
)将在读取 ALL 数据后返回。之后什么都不会发生
你是说 ReadToEnd() 也等待退出?
@knocte 你想理解微软创建的 API 吗?
对应的MSDN页面的问题是,它没有说明StandardOutput后面的缓冲区可能会变满,在这种情况下孩子必须停止写入并等待缓冲区耗尽(父读取缓冲区中的数据)。 ReadToEnd() 只能同步读取,直到缓冲区关闭或缓冲区已满,或者缓冲区未满时子退出。这是我的理解。【参考方案7】:
我们也有这个问题(或变体)。
尝试以下方法:
1) 为 p.WaitForExit(nnnn) 添加超时时间;其中 nnnn 以毫秒为单位。
2) 将 ReadToEnd 调用置于 WaitForExit 调用之前。这是我们看到MS推荐的。
【讨论】:
【参考方案8】:感谢EM0 https://***.com/a/17600012/4151626
由于内部超时以及衍生应用程序同时使用 StandardOutput 和 StandardError,其他解决方案(包括 EM0 的)对于我的应用程序仍处于死锁状态。这对我有用:
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 的初始化
【讨论】:
这是我使用的,再也没有遇到过死锁问题。【参考方案9】:我是这样解决的:
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();
我重定向了输入、输出和错误,并处理了输出和错误流的读取。 此解决方案适用于 SDK 7-8.1,适用于 Windows 7 和 Windows 8
【讨论】:
艾琳娜:感谢您的回答。此 MSDN 文档 (msdn.microsoft.com/en-us/library/…) 底部有一些注释,如果您同步读取重定向的 stdout 和 stderr 流的末尾,则会警告潜在的死锁。很难判断您的解决方案是否容易受到此问题的影响。此外,您似乎正在将进程的 stdout/stderr 输出作为输入发送回。为什么? :)【参考方案10】:通过考虑 Mark Byers、Rob、stevejay 的答案,我尝试创建一个使用异步流读取来解决您的问题的课程。这样做我意识到有一个与异步进程输出流读取相关的错误。
我在微软报告了这个错误:https://connect.microsoft.com/VisualStudio/feedback/details/3119134
总结:
你不能这样做:
process.BeginOutputReadLine();进程.Start();
您将收到 System.InvalidOperationException : StandardOut has 未被重定向或进程尚未开始。
================================================ ==================================================== =============================
那么你必须在进程结束后启动异步输出读取 开始:
process.Start(); process.BeginOutputReadLine();
这样做,创建一个竞争条件,因为输出流可以接收 将数据设置为异步之前的数据:
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();
================================================ ==================================================== =============================
然后有些人可能会说你只需要阅读流 在将其设置为异步之前。但同样的问题也会发生。那里 将是同步读取和设置之间的竞争条件 流到异步模式。
================================================ ==================================================== =============================
没有办法实现对输出流的安全异步读取 以“Process”和“ProcessStartInfo”的实际方式对进程进行 已经设计好了。
您可能会更好地使用其他用户针对您的案例建议的异步读取。但是您应该注意,由于比赛条件,您可能会错过一些信息。
【讨论】:
【参考方案11】:我认为这是一种简单且更好的方法(我们不需要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;
【讨论】:
没错,但您不应该使用.FileName = Path + @"\ggsci.exe" + @" < obeycommand.txt"
来简化您的代码吗?或者,如果您真的不想使用单独的 observablecommand.txt 文件,则可能相当于 "echo command | " + Path + @"\ggsci.exe"
。
您的解决方案不需要 AutoResetEvent 但您轮询。当您进行轮询而不是使用事件(当它们可用时)时,您会无缘无故地使用 CPU,这表明您是一个糟糕的程序员。与使用 AutoResetEvent 的其他解决方案相比,您的解决方案非常糟糕。 (但我没有给你-1,因为你试图帮助!)。【参考方案12】:
以上答案都不起作用。
Rob 解决方案挂起,“Mark Byers”解决方案得到处理的异常。(我尝试了其他答案的“解决方案”)。
所以我决定提出另一种解决方案:
public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode)
string outputLocal = ""; int localExitCode = -1;
var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
outputLocal = process.StandardOutput.ReadToEnd();
process.WaitForExit();
localExitCode = process.ExitCode;
, token);
if (task.Wait(timeoutSec, token))
output = outputLocal;
exitCode = localExitCode;
else
exitCode = -1;
output = "";
using (var process = new Process())
process.StartInfo = ...;
process.Start();
string outputUnicode; int exitCode;
GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode);
这段代码经过调试并且运行良好。
【讨论】:
好!注意,调用GetProcessOutputWithTimeout
方法时没有提供token参数。【参考方案13】:
简介
当前接受的答案不起作用(抛出异常)并且有太多变通方法但没有完整的代码。这显然是在浪费很多人的时间,因为这是一个热门问题。
结合 Mark Byers 的回答和 Karol Tyl 的回答,我根据我想如何使用 Process.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结合起来,但我还没有测试过。
代码
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);
【讨论】:
仍然得到 System.ObjectDisposedException:安全句柄也已在此版本上关闭。【参考方案14】:我知道这已经过时了,但是在阅读了整个页面之后,没有一个解决方案对我有用,尽管我没有尝试 Muhammad Rehan,因为代码有点难以理解,尽管我猜他在正确的轨道。当我说它不起作用时,这并不完全正确,有时它会正常工作,我想这与 EOF 标记之前的输出长度有关。
无论如何,对我有用的解决方案是使用不同的线程来读取 StandardOutput 和 StandardError 并写入消息。
StreamWriter sw = null;
var queue = new ConcurrentQueue<string>();
var flushTask = new System.Timers.Timer(50);
flushTask.Elapsed += (s, e) =>
while (!queue.IsEmpty)
string line = null;
if (queue.TryDequeue(out line))
sw.WriteLine(line);
sw.FlushAsync();
;
flushTask.Start();
using (var process = new Process())
try
process.StartInfo.FileName = @"...";
process.StartInfo.Arguments = $"...";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.Start();
var outputRead = Task.Run(() =>
while (!process.StandardOutput.EndOfStream)
queue.Enqueue(process.StandardOutput.ReadLine());
);
var errorRead = Task.Run(() =>
while (!process.StandardError.EndOfStream)
queue.Enqueue(process.StandardError.ReadLine());
);
var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0);
if (Task.WaitAll(new[] outputRead, errorRead , timeout) &&
process.WaitForExit((int)timeout.TotalMilliseconds))
if (process.ExitCode != 0)
throw new Exception($"Failed run... blah blah");
else
throw new Exception($"process timed out after waiting timeout");
catch (Exception e)
throw new Exception($"Failed to succesfully run the process.....", e);
希望这可以帮助那些认为这很难的人!
【讨论】:
例外:sw.FlushAsync(): Object is not set to an instance of an object. sw is null.
sw
应该如何/在哪里定义?【参考方案15】:
阅读完这里的所有帖子后,我选择了 Marko Avlijaš 的综合解决方案。 但是,它并没有解决我的所有问题。
在我们的环境中,我们有一个 Windows 服务,它计划运行数百个不同的 .bat .cmd .exe 等文件,这些文件是多年来积累的,由许多不同的人以不同的风格编写。我们无法控制程序和脚本的编写,我们只负责安排、运行和报告成功/失败。
所以我在这里尝试了几乎所有的建议,并取得了不同程度的成功。 Marko 的回答几乎是完美的,但是当作为服务运行时,它并不总是捕获标准输出。我从来没有深入了解为什么不这样做。
我们发现适用于所有案例的唯一解决方案是:http://csharptest.net/319/using-the-proces-s-runner-class/index.html
【讨论】:
我要试试这个库。我已经确定了代码的范围,它看起来正在合理地使用委托。它很好地包装在 Nuget 中。它基本上散发着职业精神的恶臭,这是我永远无法指责的。如果它咬人,会告诉你的。 源代码链接已失效。请下次将代码复制到答案中。 @VitalyZdanevich:代码已移植到github.com/csharptest/CSharpTest.Net.Tools。可以在 CmdTool/Processes 下找到它。【参考方案16】:我最终使用解决方法来避免所有复杂性:
var outputFile = Path.GetTempFileName();
info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents
所以我创建了一个临时文件,使用> outputfile > 2>&1
将输出和错误都重定向到它,然后在该过程完成后读取该文件。
其他解决方案适用于您想对输出做其他事情的场景,但对于简单的事情,这避免了很多复杂性。
【讨论】:
【参考方案17】:我已经阅读了很多答案,并做出了自己的答案。不确定这个是否会在任何情况下修复,但它可以在我的环境中修复。我只是不使用 WaitForExit 并在输出和错误结束信号上使用 WaitHandle.WaitAll 。如果有人会看到可能存在的问题,我会很高兴。或者如果它会帮助某人。对我来说更好,因为不使用超时。
private static int DoProcess(string workingDir, string fileName, string arguments)
int exitCode;
using (var process = new Process
StartInfo =
WorkingDirectory = workingDir,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = false,
FileName = fileName,
Arguments = arguments,
RedirectStandardError = true,
RedirectStandardOutput = true
,
EnableRaisingEvents = true
)
using (var outputWaitHandle = new AutoResetEvent(false))
using (var errorWaitHandle = new AutoResetEvent(false))
process.OutputDataReceived += (sender, args) =>
// ReSharper disable once AccessToDisposedClosure
if (args.Data != null) Debug.Log(args.Data);
else outputWaitHandle.Set();
;
process.ErrorDataReceived += (sender, args) =>
// ReSharper disable once AccessToDisposedClosure
if (args.Data != null) Debug.LogError(args.Data);
else errorWaitHandle.Set();
;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
WaitHandle.WaitAll(new WaitHandle[] outputWaitHandle, errorWaitHandle );
exitCode = process.ExitCode;
return exitCode;
【讨论】:
我使用它并用 Task.Run 包装来处理超时,我还返回 processid 以在超时时终止【参考方案18】:我认为使用异步,即使同时使用标准输出和标准错误,也可以有一个更优雅的解决方案并且不会出现死锁:
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;
process.Start();
var tStandardOutput = process.StandardOutput.ReadToEndAsync();
var tStandardError = process.StandardError.ReadToEndAsync();
if (process.WaitForExit(timeout))
string output = await tStandardOutput;
string errors = await tStandardError;
// Process completed. Check process.ExitCode here.
else
// Timed out.
它基于 Mark Byers 的回答。
如果您不在异步方法中,则可以使用string output = tStandardOutput.result;
而不是await
【讨论】:
【参考方案19】:这篇文章可能已经过时,但我发现它通常挂起的主要原因是由于 redirectStandardoutput 的堆栈溢出或者您有 redirectStandarderror。
由于输出数据或错误数据较大,会导致挂起时间,因为它仍在无限期处理。
所以要解决这个问题:
p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False
【讨论】:
问题在于人们明确地将这些设置为 true,因为他们希望能够访问这些流!否则,我们确实可以让它们为假。【参考方案20】:让我们将此处发布的示例代码称为重定向器,将其他程序称为重定向器。如果是我,我可能会编写一个测试重定向程序,可以用来复制问题。
所以我做到了。对于测试数据,我使用了 ECMA-334 C# Language Specificationv 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,但这并不重要。您可以使用任何其他大文本文件进行测试。
使用示例重定向器代码在我写入大量数据时挂起,但在我写入少量数据时不会挂起。
我非常努力地试图以某种方式跟踪该代码的执行,但我无法做到。我注释掉了重定向程序中禁用为重定向程序创建控制台的行,以尝试获取单独的控制台窗口,但我做不到。
然后我找到了How to start a console app in a new window, the parent’s window, or no window。因此,当一个控制台程序在没有 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 应用程序中工作,除非有明显的修改。
【讨论】:
【参考方案21】:我遇到了同样的问题,但原因不同。但是,它会在 Windows 8 下发生,但不会在 Windows 7 下发生。以下行似乎导致了问题。
pProcess.StartInfo.UseShellExecute = False
解决方案是不禁用 UseShellExecute。我现在收到了一个 Shell 弹出窗口,这是不需要的,但比等待什么都没有发生的程序要好得多。所以我为此添加了以下解决方法:
pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
现在唯一困扰我的是为什么首先会在 Windows 8 下发生这种情况。
【讨论】:
如果你想重定向输出,你需要将UseShellExecute
设置为false。以上是关于ProcessStartInfo 挂在“WaitForExit”上?为啥?的主要内容,如果未能解决你的问题,请参考以下文章
ProcessStartInfo - 在控制台窗口和文件中打印输出(C#)
如何决定是不是将 ProcessStartInfo.UseShellExecute 属性设置为 true 或 false?
将 ProcessStartInfo.WorkingDirectory 设置为 UNC 路径
使用 CREATE_SUSPENDED 标志和 JobObject 从 CreateProcessAsUser 为 C# 进程设置 ProcessStartInfo