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?如果是这样,我应该怎么做?如果没有,我在做什么错?
#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分钟内将其修复。干得好!
– Ben Gripka
13年5月13日在16:37
@AlexPeck问题是作为控制台应用程序运行。汉斯·帕桑特(Hans Passant)在这里发现了问题:stackoverflow.com/a/16218470/279516
–鲍勃·霍恩(Bob Horn)
2013年9月12日18:53
每次命令提示符关闭时,都会出现:mscorlib.dll中发生类型为“ System.ObjectDisposed”的未处理异常附加信息:安全句柄已关闭
–user1663380
2014-09-24 5:42
我们遇到了与上述@ user1663380类似的问题。您是否认为事件处理程序的using语句可能需要高于流程本身的using语句?
–丹·福布斯
2015年9月8日15:18在
我认为不需要等待句柄。按照msdn,仅使用非超时版本的WaitForExit结束:当标准输出已重定向到异步事件处理程序时,此方法返回时,输出处理可能未完成。为确保异步事件处理已完成,请在从此重载收到true后,调用不带任何参数的WaitForExit()重载。
–帕特里克(Patrick)
16年3月3日,下午1:05
#2 楼
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();
评论
我不是100%确定这是否只是我的环境造成的,但是我发现是否已将RedirectStandardOutput = true设置为;并且不要使用p.StandardOutput.ReadToEnd();您会陷入僵局/挂起。
–克里斯S
2012年2月3日15:16
真正。我也有类似情况。在进程中使用ffmpeg进行转换时,我无缘无故地重定向了StandardError,它在StandardError流中写入了足够的内容以创建死锁。
–LéonPelletier
2012年10月1日在7:29
即使重定向和读取标准输出,这仍然挂在我头上。
–user3791372
17年4月14日在4:33
@ user3791372我猜这仅在StandardOutput后面的缓冲区未完全填充时才适用。在这里,MSDN不能做到公正。我建议您阅读的一篇很棒的文章在:dzone.com/articles/async-io-and-threadpool
–卡里
19年8月21日在9:18
#3 楼
马克·拜尔斯(Mark Byers)的回答很好,但我只需添加以下内容:在处置OutputDataReceived
和ErrorDataReceived
之前,需要删除outputWaitHandle
和errorWaitHandle
的代表。如果超过超时时间后该过程继续输出数据然后终止,则在处理完outputWaitHandle
和errorWaitHandle
变量后将对其进行访问。(仅供参考,我必须将此警告添加为答案,因为我无法评论他的帖子。)
评论
也许最好调用CancelOutputRead吗?
–马克·拜尔斯
2012年6月16日20:31
将Mark的编辑代码添加到此答案中将非常棒!我现在有完全相同的问题。
–ianbailey
13年1月10日在11:32
@ianbailey解决此问题的最简单方法是将using(Process p ...)放在using(AutoResetEvent errorWaitHandle ...)内
– DiidierA。
13年4月3日在16:19
#4 楼
这是针对.NET 4.5及更高版本的更现代的,基于任务并行库(TPL)的解决方案。使用示例
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;
}
评论
迄今为止最好,最完整的答案
–TermoTux
17年9月20日在15:09
由于某种原因,这是唯一对我有用的解决方案,该应用程序停止挂起。
–杰克
18/12/19在20:38
看来,您没有处理条件,该条件在进程开始后但在附加Exited事件之前结束。我的建议-在所有注册后开始该过程。
– Stas Boyarincev
19-10-16在15:00
@StasBoyarincev谢谢,更新。我忘记了使用此更改来更新StackOverflow答案。
–穆罕默德·雷汉(Muhammad Rehan Saeed)
19-10-17在6:45
@MuhammadRehanSaeed另一件事-似乎不允许在process.Start之前调用process.BeginOutputReadLine()或process.BeginErrorReadLine()。在这种情况下,我会收到错误消息:StandardOut尚未重定向或该进程尚未开始。
– Stas Boyarincev
19-10-17在8:15
#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
– knocte
16年11月27日在6:20
并且我已经删除了超时,因为该过程可能会要求用户输入(例如输入内容),所以我不想要求用户保持快速
– knocte
16年11月27日在6:21
为什么将输出和错误更改为outputBuilder?有人可以提供有效的完整答案吗?
– MarkoAvlijaš
17年4月4日在11:32
System.ObjectDisposedException:此版本的安全句柄也已关闭
–马特
18/12/27在17:25
#6 楼
罗布回答了这个问题,为我节省了几个小时的审判。等待之前读取输出/错误缓冲区:// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
评论
但是如果在调用WaitForExit()之后又有更多数据呢?
– knocte
16年11月26日在11:04
基于我的测试的@ knocte,ReadToEnd或类似方法(如StandardOutput.BaseStream.CopyTo)将在读取所有数据后返回。什么都不会发生
– S.Serpooshan
17年12月13日在11:44
您是说ReadToEnd()也等待退出吗?
– knocte
17年12月14日下午5:41
@knocte您试图理解由Microsoft创建的API?
–aaaaaa
18年2月25日在0:23
相应的MSDN页面的问题是,它没有说明StandardOutput后面的缓冲区可能已满,并且在这种情况下,子级必须停止写入并等待缓冲区耗尽(父级读取了缓冲区中的数据)。 。 ReadToEnd()只能同步读取,直到关闭缓冲区或缓冲区已满,或者子级退出且缓冲区未满为止。那是我的理解。
–卡里
19年8月21日在9:40
#7 楼
我们也有此问题(或其他问题)。请尝试以下操作:
1)向p.WaitForExit(nnnn)添加超时;其中nnnn以毫秒为单位。
2)将ReadToEnd调用放在WaitForExit调用之前。这就是我们推荐的MS。
#8 楼
归功于EM0的https://stackoverflow.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的初始化添加到代码示例
评论
这就是我所使用的,并且从未遇到过死锁问题。
– Roemer
1月31日9:24
#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/zh-cn/library/…)的底部有一些注释,如果您同时读到重定向的stdout和stderr流的末尾,它们会警告潜在的死锁。很难确定您的解决方案是否容易受到此问题的影响。同样,您似乎正在将流程的stdout / stderr输出直接作为输入发送回去。为什么? :)
–马修·皮亚特(Matthew Piatt)
16-9-26在4:42
#10 楼
我试图通过考虑Mark Byers,Rob和stevejay的回答,来制作一个使用异步流读取来解决您的问题的类。这样做,我意识到存在与读取异步过程输出流有关的错误。我在Microsoft报告了该错误:https://connect.microsoft.com/VisualStudio/feedback/details/3119134
摘要:
您不能这样做:
process.BeginOutputReadLine(); process.Start();
您将收到System.InvalidOperationException:StandardOut已被重定向
或进程尚未开始。
=== ================================================== ================================================== ====================
然后,您必须在进程启动后启动异步输出读取
:
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” + @“
–阿米特·奈杜(Amit Naidu)
13年4月4日在22:03
您的解决方案不需要AutoResetEvent,但是可以轮询。当您进行轮询而不是使用事件(当事件可用时)时,则您无缘无故地在使用CPU,这表明您是一个不好的程序员。与使用AutoResetEvent的解决方案相比,您的解决方案确实很糟糕。 (但是我没有给您-1,因为您试图提供帮助!)。
–埃里克·厄勒(Eric Ouellet)
2014年11月7日18:38
#12 楼
上面的答案都不在起作用。Rob解决方案挂起,'Mark Byers'解决方案得到处置的异常。(我尝试了其他答案的“ solutions”)。
所以我决定建议另一个解决方案:
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参数。
– S.Serpooshan
17年12月13日在12:23
#13 楼
简介当前接受的答案不起作用(引发异常),解决方法太多,但没有完整的代码。显然,这浪费了很多人的时间,因为这是一个很普遍的问题。
结合Mark Byers的答案和Karol Tyl的答案,我根据我想使用Process.Start方法的方式编写了完整的代码。 br />
用法
我用它来创建围绕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:此版本上的安全句柄也已关闭。
–马特
18/12/27在17:25
#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():对象未设置为对象的实例。 sw为空。 sw应该如何定义/在何处定义?
– wallyk
18年5月3日在17:17
#15 楼
阅读完所有此处的帖子后,我决定使用MarkoAvlijaš的统一解决方案。但是,它并不能解决我的所有问题。
在我们的环境中,我们有一个Windows Service计划运行数百年来不同的.bat .cmd .exe等文件,这些文件是由许多不同的人以不同的风格编写的。我们无法控制程序和脚本的编写,我们仅负责计划,运行和报告成功/失败。
所以我在这里尝试了很多不同级别的建议成功。 Marko的回答几乎是完美的,但是当作为服务运行时,它并不总是捕获stdout。我从来没有深入了解为什么不这样做。
我们发现,在我们所有情况下都适用的唯一解决方案是:http://csharptest.net/319/using-the-processrunner-class/ index.html
评论
我要尝试这个图书馆。我已经确定了代码的作用域,并且看起来明智地使用了委托。它很好地包装在Nuget中。从根本上讲,它散发着专业精神,这是我永远都不能指责的东西。如果它咬,会告诉。
–史蒂夫·希伯特(Steve Hibbert)
18年8月7日在12:59
到源代码的链接已失效。请下次将代码复制到答案中。
– Vitaly Zdanevich
19年5月3日在14:29
#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以在超时时终止
–加5伏
4月23日0:50
#18 楼
我认为,使用异步时,即使同时使用standardOutput和standardError,也可能有一个更优雅的解决方案并且没有死锁: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,因为他们希望能够访问这些流!否则,我们确实可以将它们设为假。
–user276648
13年3月7日在9:58
#20 楼
让我们称这里发布的示例代码为重定向器,将另一个程序称为重定向。如果是我,那么我可能会写一个可以重定向问题的测试重定向程序。我做到了。对于测试数据,我使用了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应用程序中运行。
#21 楼
我遇到了同样的问题,但原因有所不同。但是,它将在Windows 8下发生,但在Windows 7下不会发生。以下行似乎引起了问题。pProcess.StartInfo.UseShellExecute = False
解决方案是不禁用UseShellExecute。现在,我收到了一个Shell弹出窗口,这是不需要的,但是比等待没有特别事情发生的程序好得多。因此,我为此添加了以下变通方法:
pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
现在唯一困扰我的是为什么这首先在Windows 8下发生。
评论
如果要重定向输出,则需要将UseShellExecute设置为false。
–布拉德·摩尔
2015年10月7日,下午4:52
评论
任何具有完整源代码的最终解决方案?我遇到了同样的问题,这就是我如何能够解决它stackoverflow.com/questions/2285288 / ...
是的,最终解决方案:交换最后两行。在手册中。
来自msdn:代码示例通过在p.WaitForExit之前调用p.StandardOutput.ReadToEnd来避免死锁情况。如果父进程在p.StandardOutput.ReadToEnd之前调用p.WaitForExit,并且子进程写入足够的文本以填充重定向的流,则可能导致死锁。父进程将无限期地等待子进程退出。子进程将无限期等待父进程从完整的StandardOutput流中读取。
正确执行此操作有多烦人。很高兴通过更简单的命令行重定向> outputfile :)
解决它