我有以下代码:
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?如果是,我该怎么办?如果不是,我做错了什么?
我最终使用的解决方案来避免所有的复杂性:
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将输出和错误重定向到它,然后在进程完成后读取文件。
其他解决方案适用于希望对输出执行其他操作的场景,但对于简单的操作,这可以避免很多复杂性。
我试图通过考虑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.
对于您的情况,您最好使用其他用户建议的异步读取。但是您应该意识到,由于竞态条件,您可能会错过一些信息。
在阅读了这里所有的帖子之后,我确定了Marko avlijaovic的统一解决方案。
然而,它并没有解决我所有的问题。
在我们的环境中,我们有一个Windows服务,计划运行数百个不同的.bat .cmd .exe,…等等,这些文件积累了多年,由许多不同的人以不同的风格编写。我们无法控制程序和脚本的编写,我们只负责调度、运行和成功/失败的报告。
所以我尝试了这里所有的建议,并取得了不同程度的成功。Marko的回答几乎是完美的,但是当作为服务运行时,它并不总是捕获标准输出。我一直没搞清楚为什么不行。
我们发现在所有情况下都有效的唯一解决方案是:http://csharptest.net/319/using-the-processrunner-class/index.html
我也有同样的问题,但原因不同。但是在Windows 8下会发生,而在Windows 7下不会。下面这行似乎是导致这个问题的原因。
pProcess.StartInfo.UseShellExecute = False
解决方案是不禁用UseShellExecute。我现在收到一个Shell弹出窗口,这是不需要的,但比程序等待什么特别的事情发生要好得多。所以我添加了如下的解决方法:
pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
现在唯一让我困扰的是为什么在Windows 8下会出现这种情况。
我知道这是晚餐,但是,在读完这一整页后,没有一个解决方案对我来说是有效的,尽管我没有尝试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);
}
}
}
希望这能帮助那些认为这很难的人!