2010-06-17 53 views
10

我正在試圖從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()如何返回一個布爾值,也對我來說足夠的不同步,以逐步獲得輸出。

不幸的是,我目前還沒有一個足夠大的存儲庫 - 或減緩足夠的機,能告訴的事情發生在什麼樣的順序?

UPDATE

我試過Brian的建議,它確實有效。

我的問題有點含糊。我的誤解是,我認爲Process.Start()返回整個過程的成功,而不僅僅是'開始',因此我看不到它是如何工作的。

+1

我有一些成功使用異步工作流這種任務:http://stackoverflow.com/questions/2649161/need-help-regarding-async-and-fsi – Stringer 2010-06-17 20:59:21

+0

我其實不知道什麼問題是這裏。上面的代碼是否工作?問題是什麼? – 2010-06-17 20:59:38

+1

在我看來,你可以編寫tester.exe,寫幾條線到標準輸出和刷新,等待兩秒,寫另外兩行,並用它來測試這個代碼是否按你的意願行事,是的? – Brian 2010-06-17 21:08:25

回答

12

您在寫入的表單中編寫的代碼(幾乎)正常: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#風格的反應事件處理。

+0

+1,用於回答我沒有問的問題的其餘部分:)代碼中只有一個錯誤是Process.Start()不返回進程,它返回一個布爾值。 – Benjol 2010-06-18 06:10:55

+2

我打電話給一個靜態的Process.Start(ProcessStartInfo),它返回一個Process(http://msdn.microsoft.com/en-us/library/0w4h05yb.aspx) – 2010-06-18 06:38:35

+1

這個解決方案的問題是事件'OutputDataReceived'和'ErrorDataReceived'僅在發送EOL後觸發,所以可能發生進程向用戶詢問某些內容以便在同一行中回答(例如'你確定嗎? [Y/N]'),然後它不會被F#進程封裝打印 – knocte 2017-01-06 09:34:43