即使Process.HasExited为true,Process.WaitForExit也不会返回

我使用Process.Start来启动批处理文件。 批处理文件使用“START”命令并行启动多个程序然后退出。

批处理文件完成后,Process.HasExited变为true,Process.ExitCode包含正确的退出代码。

但是当我调用Process.WaitForExit()时,它会挂起/永不返回。

下面的代码演示了这个问题。 它创建一个批处理文件,启动它然后打印:

Process is still running... Batch file is done! Process has exited. Exit code: 123 Calling WaitForExit()... 

然后它应该打印:

 WaitForExit returned. 

…但它永远不会(尽管HasExited是真的,我们已经有了一个ExitCode)。

 open System.IO open System.Diagnostics open System.Threading let foobat = """ START ping -t localhost START ping -t google.com ECHO Batch file is done! EXIT /B 123 """ File.WriteAllText("foo.bat", foobat) use p = new Process(StartInfo = ProcessStartInfo("foo.bat", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true)) let onOutput = DataReceivedEventHandler(fun _ args -> printfn "%s" args.Data) p.OutputDataReceived.AddHandler onOutput p.ErrorDataReceived.AddHandler onOutput p.Start() |> ignore p.BeginErrorReadLine() p.BeginOutputReadLine() while not p.HasExited do printfn "Process is still running..." Thread.Sleep(1000) printfn "Process has exited. Exit code: %d" p.ExitCode printfn "Calling WaitForExit()..." p.WaitForExit()|> ignore printfn "WaitForExit returned." 

我注意到这只发生在批处理文件包含“START”命令以及重定向标准输出和/或标准错误时。

为什么WaitForExit()永远不会返回?

等待这样一个过程退出的正确方法是什么?

只是轮询Process.HasExited是否安全,还是会导致其他问题?

PS。:我刚刚注意到,当进程退出时,立即返回调用WaitForExit( 100000 )的超时(肯定不会过期)。 奇怪的。 没有超时,它就会挂起。

在StandardOutput和StandardError的基于事件的异步处理的具体实现中,这似乎是一个工件(我会说“bug”)。

我注意到虽然我能够轻松地重现您的问题,只需运行您提供的代码(优秀的代码示例,顺便说一下!:)),该过程实际上并没有无限期地挂起。 相反,一旦已经启动的两个子进程都退出,它就会从WaitForExit()返回。

这似乎是Process类实现的一个有意的部分。 特别是在Process.WaitForExit()方法中,一旦它完成了对进程句柄本身的等待,它就会检查是否已经创建了一个stdout或stderr的reader; 如果是这样,并且如果WaitForExit()调用的超时值是“无限”(即-1 ),则代码实际上等待读取器上的流结束。

只有在BeginOutputReadLine()BeginErrorReadLine()方法时, BeginOutputReadLine()创建每个相应的阅读器。 在子进程关闭之前,stdout和stderr流本身不会关闭。 因此等待这些流的结束将阻止,直到发生这种情况。

WaitForExit()行为应该有所不同,具体取决于是否有人调用了启动基于事件的流的读取方法,特别是考虑到直接读取这些流不会导致WaitForExit()以这种方式运行,创建API中的不一致使得理解和使用起来更加困难。 虽然我个人称这是一个错误,但我认为Process类的实现者可能会意识到这种不一致并故意创建它。

在任何情况下,解决方法都是直接读取StandardOutput和StandardError,而不是使用API​​的基于事件的部分。 (当然,如果一个人的代码要在这些流上等待,那么在子进程关闭之前,人们会看到相同的阻塞行为。)

例如(C#,因为我不知道F#是否足以快速地将这样的代码示例拼凑在一起:)):

 using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; namespace TestSO26713374WaitForExit { class Program { static void Main(string[] args) { string foobat = @"START ping -t localhost START ping -t google.com ECHO Batch file is done! EXIT /B 123 "; File.WriteAllText("foo.bat", foobat); Process p = new Process { StartInfo = new ProcessStartInfo("foo.bat") { UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true } }; p.Start(); var _ = ConsumeReader(p.StandardOutput); _ = ConsumeReader(p.StandardError); Console.WriteLine("Calling WaitForExit()..."); p.WaitForExit(); Console.WriteLine("Process has exited. Exit code: {0}", p.ExitCode); Console.WriteLine("WaitForExit returned."); } async static Task ConsumeReader(TextReader reader) { string text; while ((text = await reader.ReadLineAsync()) != null) { Console.WriteLine(text); } } } } 

希望上述解决方法或类似方法能够解决您遇到的基本问题。 感谢评论家Niels Vorgaard Christensen指导我使用WaitForExit()方法中有问题的行,以便我可以改进这个答案。