12

我正在考虑尝试从 F# 启动一个进程,等到它完成,但也要逐步读取它的输出。

这是正确/最好的方法吗?(就我而言,我正在尝试执行 git 命令,但这与问题无关)

let gitexecute (logger:string->unit) cmd = 
    let procStartInfo = new ProcessStartInfo(@"C:\Program Files\Git\bin\git.exe", cmd) 

    // Redirect to the Process.StandardOutput StreamReader.
    procStartInfo.RedirectStandardOutput <- true
    procStartInfo.UseShellExecute <- false;

    // Do not create the black window.
    procStartInfo.CreateNoWindow <- true;

    // Create a process, assign its ProcessStartInfo and start it
    let proc = new Process();
    proc.StartInfo <- procStartInfo;
    proc.Start() |> ignore

    // Get the output into a string
    while not proc.StandardOutput.EndOfStream do
        proc.StandardOutput.ReadLine() |> logger

我不明白 proc.Start() 如何返回一个布尔值,并且足够异步,让我可以逐步获得输出。

不幸的是,我目前没有足够大的存储库 - 或足够慢的机器,无法判断事情发生的顺序......

更新

我尝试了布赖恩的建议,它确实有效。

我的问题有点含糊。我的误解是我认为 Process.Start() 返回了整个过程的成功,而不仅仅是“开始”,因此我看不出它是如何工作的。

4

1 回答 1

14

您以您编写的形式编写的代码(几乎)没问题: process.Start 启动您在另一个进程中指定的进程,因此您的输出流读取将与您的进程执行并行发生。但是,一个问题是您应该在最后调用 process.WaitForExit - 输出流关闭的事实并不意味着进程终止。

但是,如果您尝试读取进程的 stdout 和 stderr,您将遇到同步读取问题:无法同步和同时读取 2 个流 - 如果您读取 stdout 并且进程正在写入 stderr 并等待,您将死锁你消耗它的输出,反之亦然。

为了解决这个问题,您可以订阅 OutputDataRecieved 和 ErrorDataRecieved,如下所示:

type ProcessResult = { exitCode : int; stdout : string; stderr : string }

let executeProcess (exe,cmdline) =
    let psi = new System.Diagnostics.ProcessStartInfo(exe,cmdline) 
    psi.UseShellExecute <- false
    psi.RedirectStandardOutput <- true
    psi.RedirectStandardError <- true
    psi.CreateNoWindow <- true        
    let p = System.Diagnostics.Process.Start(psi) 
    let output = new System.Text.StringBuilder()
    let error = new System.Text.StringBuilder()
    p.OutputDataReceived.Add(fun args -> output.Append(args.Data) |> ignore)
    p.ErrorDataReceived.Add(fun args -> error.Append(args.Data) |> ignore)
    p.BeginErrorReadLine()
    p.BeginOutputReadLine()
    p.WaitForExit()
    { exitCode = p.ExitCode; stdout = output.ToString(); stderr = error.ToString() }

你也可以写一些类似的东西:

async {
    while true do
        let! args = Async.AwaitEvent p.OutputDataReceived
        ...
} |> Async.StartImmediate

用于 F# 风格的反应式事件处理。

于 2010-06-17T21:43:03.320 回答