2016-12-05 42 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