.net - 如何避免以错误的顺序从 Process 类接收事件?

标签 .net process f# race-condition

假设我想编写一个调用另一个程序的程序,该程序的输出同时包含 stdout 输出和 stderr 输出。

例如,我调用的这个程序是 F# 编译器试图编译包含错误的 F# 文件:

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

(前两行打印到stdout,其余的打印到stderr。)

所以我写了一个这样处理进程类的程序:

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)

但是,即使我在上面的代码中使用锁来防止在写入可变列表时出现竞争条件,有时当我运行调用 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 类以正确的顺序接收流/事件?

最佳答案

这可能是输出缓冲的情况。 您是否尝试过在写入 stdoutstderr< 后调用 Console.Out.Flush()Console.Error.Flush()/分别?

关于.net - 如何避免以错误的顺序从 Process 类接收事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40966435/

相关文章:

entity-framework - 使用 Fluent API 定义 Entity Framework 键

c# - 使用免费的第三方 dll 将 word 转换为 pdf

c# - 为什么 Enum.ToString() 不返回正确的枚举名称?

linux - 将所有 OS 进程绑定(bind)到一个 CPU

objective-c - 给定进程 ID,确定进程是否是 Mac 上的窗口进程

mysql - 如何杀死mysql进程

f# - 如何在博客中添加 F# 语法高亮

types - 类型 'float -> float' 与类型 'float' 不匹配

c++ - .Net Socket 类错误代码

c# - 在 sqldatasource 中读取 sql 模式