2016-12-05 41 views
3

假設我想編寫一個調用另一個程序的程序,該程序的輸出包含stdout輸出和stderr輸出。如何避免以錯誤的順序從Process類接收事件?

例如,該程序我打電話將F#編譯器試圖編譯含有誤差的F#的文件:(前兩行被打印到stdout,其餘stderr

F# Compiler for F# 4.0 (Open Source Edition) 
Freely distributed under the Apache 2.0 Open Source License 

/builds/someLib.fs(27,42): error FS0001: Type mismatch. Expecting a 
    string * string  
but given a 
    string * string * 'a  
The tuples have differing lengths of 2 and 3 

所以我寫一個程序與過程類處理是這樣的:

type OutChunk = StdOut of string | StdErr of string 
type OutputBuffer = list<OutChunk> 
type ProcessResult = { ExitCode: int; Output: OutputBuffer } 

module Process = 

let Execute (command: string, args: string, hidden: bool) 
    : ProcessResult = 

    // I know, this shit below is mutable, but it's a consequence of dealing with .NET's Process class' events 
    let outputBuffer = new System.Collections.Generic.List<OutChunk>() 
    let outputBufferLock = new Object() 

    use outWaitHandle = new AutoResetEvent(false) 
    use errWaitHandle = new AutoResetEvent(false) 

    let startInfo = new ProcessStartInfo(command, args) 
    startInfo.UseShellExecute <- false 
    startInfo.RedirectStandardOutput <- true 
    startInfo.RedirectStandardError <- true 

    use proc = new System.Diagnostics.Process() 
    proc.StartInfo <- startInfo 

    let outReceived (e: DataReceivedEventArgs): unit = 
     if (e.Data = null) then 
      outWaitHandle.Set() |> ignore 
     else 
      if not (hidden) then 
       Console.WriteLine(e.Data) 
      lock outputBufferLock (fun _ -> outputBuffer.Add(OutChunk.StdOut(e.Data))) 

    let errReceived (e: DataReceivedEventArgs): unit = 
     if (e.Data = null) then 
      errWaitHandle.Set() |> ignore 
     else 
      if not (hidden) then 
       Console.Error.WriteLine(e.Data) 
      lock outputBufferLock (fun _ -> outputBuffer.Add(OutChunk.StdErr(e.Data))) 

    proc.OutputDataReceived.Add outReceived 
    proc.ErrorDataReceived.Add errReceived 

    proc.Start() |> ignore 
    let exitCode = 
     try 
      proc.BeginOutputReadLine() 
      proc.BeginErrorReadLine() 
      proc.WaitForExit() 
      proc.ExitCode 
     finally 
      outWaitHandle.WaitOne() |> ignore 
      errWaitHandle.WaitOne() |> ignore 
    { ExitCode = exitCode; Output = List.ofSeq(outputBuffer) } 

let rec PrintToScreen (outputBuffer: OutputBuffer) = 
    match outputBuffer with 
    | [] ->() 
    | head::tail -> 
     match head with 
     | StdOut(out) -> Console.WriteLine(out) 
     | StdErr(err) -> Console.Error.WriteLine(err) 
     PrintToScreen(tail) 

然而,即使我使用的鎖在c上述頌歌防止競爭條件寫入可變列表時,有時當我運行調用F#編譯器的F#程序,然後我打電話PrintToScreen功能,我得到的流混合:

F# Compiler for F# 4.0 (Open Source Edition) 

/builds/someLib.fs(27,42): error FS0001: Type mismatch. Expecting a 
Freely distributed under the Apache 2.0 Open Source License 
    string * string  
but given a 
    string * string * 'a  
The tuples have differing lengths of 2 and 3 

(正如你可以看到,許可證文本應該在編譯器錯誤之前到達,但它沒有)

這怎麼可能?如何處理惡魔System.Diagnostics.Process類以正確的順序接收流/事件?

+1

問題可能是緩衝差異。嘗試看stdout和stderr以相同的方式緩衝 –

+0

他們已經以同樣的方式緩衝(據我所知),所以不知道你建議什麼? – knocte

+1

正常情況下,輸出緩衝時無錯誤。有一個選項可以在某處進行更改。 –

回答

2

這可能是輸出緩衝的一種情況。 您是否分別在寫入stdoutstderr之後嘗試過撥打Console.Out.Flush()Console.Error.Flush()

+0

我會嘗試並報告,謝謝 – knocte