具有重定向标准输入/输出的过程根据输入的大小而表现不同

nto*_*ohl 4 f# process

关于从F#运行进程我有一个奇怪的行为.我的基本问题是要运行的Graphvizdot.exe一个生成的图形和可视化它.

当我将图表限制为小时,一切正常.但是对于更大的图表,它挂在特定的线上.我很好奇为什么会这样,所以也许我可以解决我的问题.我为此目的创建了一个MVCE.

我有2个控制台程序.一个是模拟器dot.exe,在那里我将展示输入,并期望.jpg.这个版本只是输出10次输出到输出的块:

// Learn more about F# at http://fsharp.org
// See the 'F# Tutorial' project for more help.
open System
open System.IO

[<EntryPoint>]
let main argv = 
    let bufferSize = 4096
    let mutable buffer : char [] = Array.zeroCreate bufferSize
    // it repeats in 4096 blocks, but it doesn't matter. It's a simulation of outputing 10 times amount output.
    while Console.In.ReadBlock(buffer, 0, bufferSize) <> 0 do
        for i in 1 .. 10 do
            Console.Out.WriteLine(buffer)
    0 // return an integer exit code
Run Code Online (Sandbox Code Playgroud)

所以我有一个.exe名为C:\Users\MyUserName\Documents\Visual Studio 2015\Projects\ProcessInputOutputMVCE\EchoExe\bin\Debug\EchoExe.exeThan的另一个控制台项目,它使用它:

// Learn more about F# at http://fsharp.org
// See the 'F# Tutorial' project for more help.
open System.IO

[<EntryPoint>]
let main argv = 
    let si = 
        new System.Diagnostics.ProcessStartInfo(@"C:\Users\MyUserName\Documents\Visual Studio 2015\Projects\ProcessInputOutputMVCE\EchoExe\bin\Debug\EchoExe.exe", "",
// from Fake's Process handling
#if FX_WINDOWSTLE
            WindowStyle = ProcessWindowStyle.Hidden,
#else
            CreateNoWindow = true,
#endif
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            RedirectStandardInput = true)
    use p = new System.Diagnostics.Process()
    p.StartInfo <- si
    if p.Start() then 
        let input =
            Seq.replicate 3000 "aaaa"
            |> String.concat "\n"
        p.StandardInput.Write input
        // hangs on Flush()
        p.StandardInput.Flush()
        p.StandardInput.Close()
        use outTxt = File.Create "out.txt"
        p.StandardOutput.BaseStream.CopyTo outTxt
        // double WaitForExit because of https://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput(v=vs.110).aspx
        // saying first WaitForExit() waits for StandardOutput. Next is needed for the whole process.
        p.WaitForExit()
        p.WaitForExit()
    0 // return an integer exit code
Run Code Online (Sandbox Code Playgroud)

哪个挂在上面p.StandardInput.Flush().除非我将输入音量更改为Seq.replicate 300 "aaaa".为什么它的工作方式不同?

Process.StandardOutput的引用声明读取StandardOutput和子进程在同一时间写入该流可能会导致死锁.在这种情况下,谁是儿童过程?是我的p.StandardInput.Write input吗?

其他可能的僵局将是

从标准输出和标准错误流中读取所有文本.

但我不是在读错误流.无论如何它建议用异步处理输入/输出所以我有一个以下版本:

    // same as before
    ...
    if p.Start() then 
        let rec writeIndefinitely (rows: string list) = 
            async {
                if rows.Length = 0 then
                    ()
                else
                    do! Async.AwaitTask (p.StandardInput.WriteLineAsync rows.Head)
                    p.StandardInput.Flush()
                    do! writeIndefinitely rows.Tail
                }

        let inputTaskContinuation =
            Seq.replicate 3000 "aaaa"
            |> Seq.toList
            |> writeIndefinitely
        Async.Start (async {
                        do! inputTaskContinuation
                        p.StandardInput.Close()
                        }
                     )   
        let bufferSize = 4096
        let mutable buffer : char array = Array.zeroCreate bufferSize
        use outTxt = File.CreateText "out.txt"
        let rec readIndefinitely() = 
            async {
                let! readBytes = Async.AwaitTask (p.StandardOutput.ReadAsync (buffer, 0, bufferSize))
                if readBytes <> 0 then
                    outTxt.Write (buffer, 0, buffer.Length)
                    outTxt.Flush()
                    do! readIndefinitely()
            }
        Async.Start (async { 
                        do! readIndefinitely()
                        p.StandardOutput.Close()
                        })
        // with that it throws "Cannot mix synchronous and asynchronous operation on process stream." on let! readBytes = Async.AwaitTask (p.StandardOutput.ReadAsync (buffer, 0, bufferSize))
        //p.BeginOutputReadLine()
        p.WaitForExit()
        // using dot.exe, it hangs on the second WaitForExit()
        p.WaitForExit()
Run Code Online (Sandbox Code Playgroud)

哪个不挂,写道out.txt.除了真正的代码使用dot.exe.这是异步的.为什么会抛出异常p.BeginOutputReadLine()

一些试验后,整个异步出可以留p.StandardOutput.BaseStream.CopyTo outTxt在那里outTxtFile.Create不是File.CreateText.只有异步输入对同步输入处理才是正确的.这很奇怪.

把它们加起来.如果我有异步输入处理,它工作正常(除非dot.exe,但如果我弄清楚,也许我也可以修复它),如果它有同步输入处理,它取决于输入的大小.(300件作品,3000件没有)为什么?

更新

由于我真的不需要重定向标准错误,我已经删除了RedirectStandardError = true.这解决了这个神秘的dot.exe问题.

Ale*_*hov 6

我认为这里的僵局如下:

  1. 主机进程将过多数据写入输入缓冲区.
  2. 子进程从缓冲区读取并写入输出.
  3. 主机进程不从缓冲区读取(它在发送所有数据后发生).当子进程的输出缓冲区被填满时,它会在写入时阻塞并停止从输入读取.两个进程现在处于死锁状态.

没有必要使用完全异步的代码.我认为可行的方法是在编写点之前将其写入dot的stdin,并在再次写入之前读到dot的stdout.