同步启动进程,并且“流式传输”输出

发布于 2024-09-05 18:31:41 字数 1051 浏览 5 评论 0原文

我正在考虑尝试从 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() 返回整个过程的成功,而不仅仅是“开始”,因此我看不到它如何工作。

I'm looking at trying to start a process from F#, wait till it's finished, but also read it's output progressively.

Is this the right/best way to do it? (In my case I'm trying to execute git commands, but that is tangential to the question)

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

What I don't understand is how the proc.Start() can return a boolean and also be asynchronous enough for me to get the output out of the while progressively.

Unfortunately, I don't currently have a large enough repository - or slow enough machine, to be able to tell what order things are happening in...

UPDATE

I tried Brian's suggestion, and it does work.

My question was a bit vague. My misunderstanding was that I assumed that Process.Start() returned the success of the process as a whole, and not just of the 'Start', and thus I couldn't see how it could work.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梦屿孤独相伴 2024-09-12 18:31:41

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

The code you wrote in the form you wrote it is (almost) ok: process.Start start the process you specify in, well, another process, so your output stream reads will happen in parallel with your process execution. One issue though is that you should throw in a call to process.WaitForExit in the end - the fact that output stream is closed does not imply that process terminated.

However you will run into problems with synchronyous reading if you try to read both stdout and stderr of the process: there is no way of reading 2 streams synchronously and simultaneously - you will deadlock if you read stdout and process is writing to stderr and waits for you to consume its output or vice versa.

To mediate this, you can subscribe to OutputDataRecieved and ErrorDataRecieved, like this:

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() }

You can also write something along the lines of:

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

for F#-style reactive event handling.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文