Process.WaitForExit没有';即使Process.HasExited为true,也不会返回

本文关键字:Process true 返回 HasExited 没有 WaitForExit 即使 | 更新日期: 2023-09-27 18:28:56

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

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

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

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

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(10000)并带有巨大的超时(肯定不会过期)会立即返回。维尔德。没有超时,它挂起。

Process.WaitForExit没有';即使Process.HasExited为true,也不会返回

在StandardOutput和StandardError的基于事件的异步处理的特定实现中,这似乎是一个工件(我认为是"bug")。

我注意到,虽然我可以很容易地重现您的问题,只需运行您提供的代码(顺便说一句,这是一个很好的代码示例!:)),但这个过程实际上并没有无限期地挂起。相反,一旦启动的两个子进程都退出,它就会从WaitForExit()返回。

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

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

WaitForExit()的行为应该不同,这取决于是否调用了启动流的基于事件读取的方法中的任何一个,特别是考虑到直接读取这些流不会导致WaitForExit()以这种方式行为,这会在API中造成不一致,使其更难理解和使用。虽然我个人认为这是一个bug,但我认为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()方法中有问题的行,这样我就可以改进这个答案。