【问题标题】:Starting a process synchronously, and "streaming" the output同步启动进程,并“流式传输”输出
【发布时间】:2011-03-05 04:06:34
【问题描述】:

我正在考虑尝试从 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() 是如何返回一个布尔值并且足够异步的,我可以逐步将输出从 while 中取出。

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

更新

我尝试了 Brian 的建议,它确实有效。

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

【问题讨论】:

  • 我在使用异步工作流完成此类任务时取得了一些成功:stackoverflow.com/questions/2649161/…
  • 我实际上不确定这里的问题是什么。您上面的代码是否有效?有什么问题?
  • 在我看来,您可以编写 tester.exe,将几行写入 stdout 并刷新,等待两秒钟,再写入两行,...并使用它来测试此代码的行为如你所愿,是吗?
  • @Brian,你是对的,我做到了,而且确实如此。谢谢。
  • @Stringer,很有趣,谢谢。

标签: .net f# synchronous processstartinfo


【解决方案1】:

您以您编写的形式编写的代码(几乎)没问题: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# 风格的反应式事件处理。

【讨论】:

  • +1 用于回答我没有问的其余问题 :) 您的代码中只有一个错误是 Process.Start() 没有返回进程,它返回一个布尔值。
  • 我正在调用一个返回 Process (msdn.microsoft.com/en-us/library/0w4h05yb.aspx) 的静态 Process.Start(ProcessStartInfo)
  • 此解决方案的问题是事件OutputDataReceivedErrorDataReceived 仅在发送 EOL 后触发,因此可能会发生该过程要求用户同时回答某些问题行(例如Are you sure? [Y/N]),然后它不会被 F# 进程包装器打印出来
猜你喜欢
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-06
  • 2020-09-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多