即使 Process.HasExited 为真,Process.WaitForExit 也不会返回 [英] Process.WaitForExit doesn't return even though Process.HasExited is true

查看:15
本文介绍了即使 Process.HasExited 为真,Process.WaitForExit 也不会返回的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I use Process.Start to start a batch file. The batch file uses the "START" command to start several programs in parallel and then exits.

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

Once the batch file is done Process.HasExited becomes true and Process.ExitCode contains the correct exit code.

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

But when I call Process.WaitForExit() it hangs / never returns.

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

The following piece of code demonstrates the problem. It creates a batch file, starts it and then prints:

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

然后应该打印:

WaitForExit returned.

...但它从来没有(即使 HasExited 是真的,而且我们已经有了一个 ExitCode).

... but it never does (even though HasExited is true and we already have an 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"命令并且标准输出和/或标准错误被重定向时才会发生这种情况.

I noticed that this only happens when the batch file contains "START" commands and when standard output and/or standard error are redirected.

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

Why does WaitForExit() never return?

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

What's the right way to wait for such a process to exit?

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

Is it safe to just poll Process.HasExited or can that result in other problems?

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

PS.: I just noticed that calling WaitForExit(100000) with a huge timeout (that definitely doesn't expire) returns immediately when the process exits. Wierd. Without timeout it hangs.

推荐答案

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

This seems to be an artifact (I'd say "bug") in the specific implementation of the event-based asynchronous handling of StandardOutput and StandardError.

>

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

I noticed that while I was able to easily reproduce your problem, simply by running the code you provided (excellent code example, by the way! :) ), the process did not actually hang indefinitely. Rather, it returned from WaitForExit() once both of the child processes that had been started had themselves exited.

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

This seems to be an intentional part of the implementation of the Process class. In particular, in the Process.WaitForExit() method, once it has finished waiting on the process handle itself, it checks to see if a reader for either stdout or stderr has been created; if so, and if the timeout value for the WaitForExit() call is "infinite" (i.e. -1), the code actually waits for the end-of-stream on the reader(s).

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

Each respective reader is created only when the BeginOutputReadLine() or BeginErrorReadLine() method is called. The stdout and stderr streams are themselves not closed until the child processes have closed. So waiting on the end of those streams will block until that happens.

WaitForExit() 的行为应该有所不同,具体取决于是否调用了启动基于事件的流读取的方法之一,尤其是考虑到直接读取这些流确实 not 导致 WaitForExit() 以这种方式运行,在 API 中造成不一致,使其更难以理解和使用.虽然我个人将此称为错误,但我认为 Process 类的实现者可能意识到这种不一致并有意创建它.

That WaitForExit() should behave differently depending on whether one has called either of the methods that start the event-based reading of the streams or not, and especially given that reading those streams directly does not cause WaitForExit() to behave that way, creates an inconsistency in the API that makes it much more difficult to understand and use. While I'd personally call this a bug, I suppose it's possible that the implementor(s) of the Process class are aware of this inconsistency and created it on purpose.

无论如何,解决方法是直接读取 StandardOutput 和 StandardError,而不是使用 API 的基于事件的部分.(当然,如果一个人的代码等待那些流,在子进程关闭之前会看到相同的阻塞行为.)

In any case, the work-around would be to read StandardOutput and StandardError directly instead of using the event-based part of the API. (Though of course, if one's code were to wait on those streams, one would see the same blocking behavior until the child processes close.)

例如(C#,因为我不太了解 F#,无法快速将这样的代码示例放在一起:)):

For example (C#, because I don't know F# well enough to slap a code example like this together quickly :) ):

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() 方法中的问题行,以便我改进这个答案.

Hopefully the above work-around or something similar will address the basic issue you've run into. My thanks to commenter Niels Vorgaard Christensen for directing me to the problematic lines in the WaitForExit() method, so that I could improve this answer.

这篇关于即使 Process.HasExited 为真,Process.WaitForExit 也不会返回的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆