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

stm*_*max 12 .net c# f#

我使用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()...
Run Code Online (Sandbox Code Playgroud)

然后它应该打印:

WaitForExit returned.
Run Code Online (Sandbox Code Playgroud)

...但它永远不会(尽管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."
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

PS:我只注意到调用WaitForExit(100000用)巨大的超时(即绝对不会过期)立即返回进程退出时.奇怪的.没有超时就会挂起.

Pet*_*iho 20

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

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

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

只有在调用BeginOutputReadLine()BeginErrorReadLine()方法时才会创建每个相应的阅读器.在子进程关闭之前,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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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