我使用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 )并带有巨大超时(肯定不会过期)。奇怪的。没有超时就会挂起。
答案 0 :(得分:19)
在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);
}
}
}
}
希望上述解决方法或类似方法能够解决您遇到的基本问题。感谢commenter Niels Vorgaard Christensen引导我查看WaitForExit()
方法中有问题的行,以便我可以改进这个答案。