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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With