ProcessStartInfo 挂在“WaitForExit”上? 为什么?
我有以下代码:
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? 如果是这样,我该怎么办? 如果不是,我做错了什么?
I have the following code:
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
I know that the output from the process I am starting is around 7MB long. Running it in the Windows console works fine. Unfortunately programmatically this hangs indefinitely at WaitForExit
. Note also this code does NOT hang for smaller outputs (like 3KB).
Is it possible that the internal StandardOutput
in ProcessStartInfo
can't buffer 7MB? If so, what should I do instead? If not, what am I doing wrong?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(23)
问题是,如果您重定向
StandardOutput
和/或StandardError
,内部缓冲区可能会变满。 无论您使用什么顺序,都可能会出现问题:StandardOutput
,则进程可能会阻止尝试写入它,因此进程永远不会结束。StandardOutput
读取数据,那么如果进程永远不会关闭StandardOutput
(例如,如果它从未终止,或者如果它写入StandardError
时被阻止)。解决方案是使用异步读取来确保缓冲区不会变满。 为了避免任何死锁并收集
StandardOutput
和StandardError
的所有输出,您可以执行以下操作:编辑:请参阅下面的答案,了解如何避免 ObjectDisposeException如果发生超时。
The problem is that if you redirect
StandardOutput
and/orStandardError
the internal buffer can become full. Whatever order you use, there can be a problem:StandardOutput
the process can block trying to write to it, so the process never ends.StandardOutput
using ReadToEnd then your process can block if the process never closesStandardOutput
(for example if it never terminates, or if it is blocked writing toStandardError
).The solution is to use asynchronous reads to ensure that the buffer doesn't get full. To avoid any deadlocks and collect up all output from both
StandardOutput
andStandardError
you can do this:EDIT: See answers below for how avoid an ObjectDisposedException if the timeout occurs.
Process.StandardOutput 的文档 表示在等待之前先阅读,否则可能会死锁,复制的代码片段如下:
The documentation for
Process.StandardOutput
says to read before you wait otherwise you can deadlock, snippet copied below:这是一个更现代的、基于任务并行库 (TPL) 的解决方案,适用于 .NET 4.5 及更高版本。
使用示例
实现
This is a more modern awaitable, Task Parallel Library (TPL) based solution for .NET 4.5 and above.
Usage Example
Implementation
Mark Byers 的回答非常好,但我只想添加以下内容:
OutputDataReceived
和ErrorDataReceived
委托需要在outputWaitHandle
和 < code>errorWaitHandle 被处置。 如果进程在超过超时后继续输出数据然后终止,则outputWaitHandle
和errorWaitHandle
变量将在释放后被访问。(仅供参考,我必须添加此警告作为答案,因为我无法对他的帖子发表评论。)
Mark Byers' answer is excellent, but I would just add the following:
The
OutputDataReceived
andErrorDataReceived
delegates need to be removed before theoutputWaitHandle
anderrorWaitHandle
get disposed. If the process continues to output data after the timeout has been exceeded and then terminates, theoutputWaitHandle
anderrorWaitHandle
variables will be accessed after being disposed.(FYI I had to add this caveat as an answer as I couldn't comment on his post.)
当进程超时时,会发生未处理的 ObjectDisposeException 的问题。 在这种情况下,条件的其他部分:
不会被执行。 我通过以下方式解决了这个问题:
The problem with unhandled ObjectDisposedException happens when the process is timed out. In such case the other parts of the condition:
are not executed. I resolved this problem in a following way:
罗布回答了我的问题,让我节省了几个小时的时间。 等待之前读取输出/错误缓冲区:
Rob answered it and saved me few more hours of trials. Read the output/error buffer before waiting:
我们也有这个问题(或变体)。
请尝试以下操作:
1) 为 p.WaitForExit(nnnn) 添加超时; 其中 nnnn 的单位是毫秒。
2) 将 ReadToEnd 调用放在 WaitForExit 调用之前。 这是我们所看到的 MS 推荐的内容。
We have this issue as well (or a variant).
Try the following:
1) Add a timeout to p.WaitForExit(nnnn); where nnnn is in milliseconds.
2) Put the ReadToEnd call before the WaitForExit call. This is what we've seen MS recommend.
归功于 EM0 https://stackoverflow.com/a/17600012/4151626
由于内部超时以及生成的应用程序同时使用 StandardOutput 和 StandardError,我的应用程序的其他解决方案(包括 EM0)仍然陷入僵局。 这对我有用:
编辑:将 StartInfo 的初始化添加到代码示例中
Credit to EM0 for https://stackoverflow.com/a/17600012/4151626
The other solutions (including EM0's) still deadlocked for my application, due to internal timeouts and the use of both StandardOutput and StandardError by the spawned application. Here is what worked for me:
Edit: added initialization of StartInfo to code sample
我这样解决了这个问题:
我重定向了输入、输出和错误,并处理了从输出和错误流中的读取。
该解决方案适用于 SDK 7-8.1,同时适用于 Windows 7 和 Windows 8
I solved it this way:
I redirected both input, output and error and handled reading from output and error streams.
This solution works for SDK 7- 8.1, both for Windows 7 and Windows 8
我尝试创建一个类,通过考虑 Mark Byers、Rob、stevejay 的答案,使用异步流读取来解决您的问题。 这样做我意识到存在与异步进程输出流读取相关的错误。
我在 Microsoft 报告了该错误: https://connect.microsoft.com/VisualStudio/feedback /details/3119134
摘要:
您可能最好使用异步读取,就像其他用户针对您的情况所建议的那样。 但您应该意识到,由于竞争条件,您可能会错过一些信息。
I tried to make a class that would solve your problem using asynchronous stream read, by taking in account Mark Byers, Rob, stevejay answers. Doing so I realised that there is a bug related to asynchronous process output stream read.
I reported that bug at Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134
Summary:
You are probably better using asynchronous read like suggested by other users for your case. But you should be aware that you could miss some information due to race condition.
我认为使用异步,可以有一个更优雅的解决方案,即使同时使用 standardOutput 和 standardError 也不会出现死锁:
它基于 Mark Byers 的答案。
如果您不在异步方法中,则可以使用
string output = tStandardOutput.result;
而不是await
I think with async, it is possible to have a more elegant solution and not having deadlocks even when using both standardOutput and standardError:
It is base on Mark Byers answer.
If you are not in an async method, you can use
string output = tStandardOutput.result;
instead ofawait
我读过很多答案并做出了自己的答案。 不确定这是否会在任何情况下修复,但它在我的环境中修复。 我只是不使用 WaitForExit 并在输出和输出上使用 WaitHandle.WaitAll 错误结束信号。 如果有人会看到这方面可能存在的问题,我会很高兴。 或者如果它会对某人有帮助。 对我来说更好,因为不使用超时。
I've read many of the answers and made my own. Not sure this one will fix in any case, but it fixes in my environment. I'm just not using WaitForExit and use WaitHandle.WaitAll on both output & error end signals. I will be glad, if someone will see possible problems with that. Or if it will help someone. For me it's better because not uses timeouts.
我认为这是简单且更好的方法(我们不需要 AutoResetEvent ):
I thing that this is simple and better approach (we don't need
AutoResetEvent
):上面的答案都不起作用。
Rob 解决方案挂起,“Mark Byers”解决方案获得已处理的异常。(我尝试了其他答案的“解决方案”)。
所以我决定提出另一个解决方案:
这段代码经过调试并且工作完美。
None of the answers above is doing the job.
Rob solution hangs and 'Mark Byers' solution get the disposed exception.(I tried the "solutions" of the other answers).
So I decided to suggest another solution:
This code debugged and works perfectly.
简介
目前接受的答案不起作用(引发异常),并且有太多解决方法,但没有完整的代码。 这显然浪费了很多人的时间,因为这是一个很受欢迎的问题。
结合 Mark Byers 的答案和 Karol Tyl 的答案,我根据我想要如何使用 Process.Start 方法编写了完整的代码。
用法
我用它来创建围绕 git 命令的进度对话框。 这就是我使用它的方式:
理论上你也可以结合 stdout 和 stderr,但我还没有测试过。
代码
Introduction
Currently accepted answer doesn't work (throws exception) and there are too many workarounds but no complete code. This is obviously wasting lots of people's time because this is a popular question.
Combining Mark Byers' answer and Karol Tyl's answer I wrote full code based on how I want to use the Process.Start method.
Usage
I have used it to create progress dialog around git commands. This is how I've used it:
In theory you can also combine stdout and stderr, but I haven't tested that.
Code
我知道这已经很老了,但是,在阅读了整个页面之后,没有一个解决方案对我有用,尽管我没有尝试 Muhammad Rehan,因为代码有点难以遵循,尽管我猜他走在正确的轨道上。 当我说它不起作用时,这并不完全正确,有时它会很好地工作,我想这与 EOF 标记之前的输出长度有关。
无论如何,对我有用的解决方案是使用不同的线程来读取 StandardOutput 和 StandardError 并写入消息。
希望这可以帮助那些认为这很难的人!
I know that this is supper old but, after reading this whole page none of the solutions was working for me, although I didn't try Muhammad Rehan as the code was a little hard to follow, although I guess he was on the right track. When I say it didn't work that's not entirely true, sometimes it would work fine, I guess it is something to do with the length of the output before an EOF mark.
Anyway, the solution that worked for me was to use different threads to read the StandardOutput and StandardError and write the messages.
Hope this helps someone, who thought this could be so hard!
阅读完这里的所有帖子后,我选择了 Marko Avlijaš 的综合解决方案。
但是,它并没有解决我的所有问题。
在我们的环境中,我们有一个 Windows 服务,它计划运行数百个不同的 .bat .cmd .exe 等文件,这些文件是多年来积累的、由许多不同的人以不同的风格编写的。 我们无法控制程序的编写和编写。 脚本,我们只负责调度、运行和报告成功/失败。
所以我尝试了这里几乎所有的建议,并取得了不同程度的成功。 Marko 的答案几乎是完美的,但是当作为服务运行时,它并不总是捕获标准输出。 我从来没有弄清楚为什么不。
我们发现适用于所有情况的唯一解决方案是: http: //csharptest.net/319/using-the-processrunner-class/index.html
After reading all the posts here, i settled on the consolidated solution of Marko Avlijaš.
However, it did not solve all of my issues.
In our environment we have a Windows Service which is scheduled to run hundreds of different .bat .cmd .exe,... etc. files which have accumulated over the years and were written by many different people and in different styles. We have no control over the writing of the programs & scripts, we are just responsible for scheduling, running, and reporting on success/failure.
So i tried pretty much all of the suggestions here with different levels of success. Marko's answer was almost perfect, but when run as a service, it didnt always capture stdout. I never got to the bottom of why not.
The only solution we found that works in ALL our cases is this : http://csharptest.net/319/using-the-processrunner-class/index.html
我最终使用的解决方法是为了避免所有复杂性:
因此我创建了一个临时文件,通过使用
> 将输出和错误重定向到它。 输出文件> 2>&1
然后在进程完成后读取文件。其他解决方案适用于您想要对输出执行其他操作的场景,但对于简单的操作,这可以避免很多复杂性。
Workaround I ended up using to avoid all the complexity:
So I create a temp file, redirect both the output and error to it by using
> outputfile > 2>&1
and then just read the file after the process has finished.The other solutions are fine for scenarios where you want to do other stuff with the output, but for simple stuff this avoids a lot of complexity.
就我而言,我遇到了错误,所以我只是徒劳地等待正常的输出。
我将顺序从: 改为
:
In my case I had an error so I just waited in vain for a normal ouput.
I switched the order from this:
To this:
这篇文章可能已经过时,但我发现它通常挂起的主要原因是由于重定向标准输出的堆栈溢出或者如果您有重定向标准错误。
由于输出数据或错误数据很大,因此会导致挂起时间,因为它仍在无限期地处理。
所以要解决这个问题:
This post maybe outdated but i found out the main cause why it usually hang is due to stack overflow for the redirectStandardoutput or if you have redirectStandarderror.
As the output data or the error data is large, it will cause a hang time as it is still processing for indefinite duration.
so to resolve this issue:
让我们将此处发布的示例代码称为重定向程序,将另一个程序称为重定向程序。 如果是我,那么我可能会编写一个可用于重复问题的测试重定向程序。
所以我做了。 对于测试数据,我使用了 ECMA-334 C# 语言规范 v PDF; 大约是5MB。 以下是其中的重要部分。
数据大小值与实际文件大小不匹配,但这并不重要。 目前尚不清楚 PDF 文件是否总是在行尾同时使用 CR 和 LF,但这并不重要。 您可以使用任何其他大型文本文件进行测试。
使用该示例重定向器代码在写入大量数据时会挂起,但在写入少量数据时不会挂起。
我非常尝试以某种方式跟踪该代码的执行,但我做不到。 我注释掉了重定向程序中禁止为重定向程序创建控制台的行,以尝试获取单独的控制台窗口,但我不能。
然后我发现 如何在新窗口、父窗口或无窗口中启动控制台应用。 因此,显然,当一个控制台程序在没有 ShellExecute 的情况下启动另一个控制台程序时,我们不能(轻松)拥有一个单独的控制台,并且由于 ShellExecute 不支持重定向,我们必须共享一个控制台,即使我们没有为另一个进程指定窗口。
我假设,如果重定向程序在某处填满了缓冲区,那么它必须等待读取数据,如果此时重定向器没有读取数据,那么它就是死锁。
解决方案是不使用ReadToEnd,在写入数据时读取数据,但不一定要使用异步读取。 解决方案可能非常简单。 以下内容适用于 5 MB PDF。
另一种可能性是使用 GUI 程序来进行重定向。 除了明显的修改之外,前面的代码可以在 WPF 应用程序中运行。
Let us call the sample code posted here the redirector and the other program the redirected. If it were me then I would probably write a test redirected program that can be used to duplicate the problem.
So I did. For test data I used the ECMA-334 C# Language Specificationv PDF; it is about 5MB. The following is the important part of that.
The datasize value does not match the actual file size but that does not matter. It is not clear if a PDF file always uses both CR and LF at the end of lines but that does not matter for this. You can use any other large text file to test with.
Using that the sample redirector code hangs when I write the large amount of data but not when I write a small amount.
I tried very much to somehow trace the execution of that code and I could not. I commented out the lines of the redirected program that disabled creation of a console for the redirected program to try to get a separate console window but I could not.
Then I found How to start a console app in a new window, the parent’s window, or no window. So apparently we cannot (easily) have a separate console when one console program starts another console program without ShellExecute and since ShellExecute does not support redirection we must share a console, even if we specify no window for the other process.
I assume that if the redirected program fills up a buffer somewhere then it must wait for the data to be read and if at that point no data is read by the redirector then it is a deadlock.
The solution is to not use ReadToEnd and to read the data while the data is being written but it is not necessary to use asynchronous reads. The solution can be quite simple. The following works for me with the 5 MB PDF.
Another possibility is to use a GUI program to do the redirection. The preceding code works in a WPF application except with obvious modifications.
这是我几乎同步的解决方案。 您应该调用
StandardOutput.ReadToEndAsync()
和StandardError.ReadToEndAsync()
同时在一起!
This is my almost synchronous solution. You should invoke
StandardOutput.ReadToEndAsync()
andStandardError.ReadToEndAsync()
together at the same time!
我也遇到了同样的问题,但原因不同。 但是,在 Windows 8 下会发生这种情况,但在 Windows 7 下则不会。以下行似乎导致了该问题。
解决方案是不禁用 UseShellExecute。 我现在收到一个 Shell 弹出窗口,这是不需要的,但比程序等待没有什么特别的事情发生要好得多。 因此,我为此添加了以下解决方法:
现在唯一困扰我的是为什么在 Windows 8 下会发生这种情况。
I was having the same issue, but the reason was different. It would however happen under Windows 8, but not under Windows 7. The following line seems to have caused the problem.
The solution was to NOT disable UseShellExecute. I now received a Shell popup window, which is unwanted, but much better than the program waiting for nothing particular to happen. So I added the following work-around for that:
Now the only thing bothering me is to why this is happening under Windows 8 in the first place.