具有重定向标准输入/输出的进程根据输入

时间:2018-04-25 16:14:16

标签: process f#

我有一个关于从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

所以我有一个名为.exe的{​​{1}}来了另一个使用它的控制台项目:

C:\Users\MyUserName\Documents\Visual Studio 2015\Projects\ProcessInputOutputMVCE\EchoExe\bin\Debug\EchoExe.exe

哪个挂起// 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 。除非我将输入音量更改为p.StandardInput.Flush()。为什么它的工作方式不同?

Process.StandardOutput's reference表示从Seq.replicate 300 "aaaa"读取并且子进程在同一时间写入该流可能会导致死锁。在这种情况下,谁是儿童过程?是我的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() 。除了使用out.txt的真实代码。这是异步的。为什么它会为dot.exe抛出异常?

经过一些实验,整个异步输出可以保持p.BeginOutputReadLine() p.StandardOutput.BaseStream.CopyTo outTxt outTxt而非File.Create。只有异步输入对同步输入处理才是正确的。这很奇怪。

总结一下。如果我有异步输入处理,它工作正常(除了File.CreateText,但如果我弄明白,也许我也可以修复它),如果它有同步输入处理,它是取决于 on输入的大小。 (300件作品,3000件没有)为什么会这样?

更新

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

1 个答案:

答案 0 :(得分:6)

我认为这里的僵局如下:

  1. 主机进程将过多数据写入输入缓冲区。
  2. 子进程从缓冲区读取并写入输出。
  3. 主机进程不从缓冲区读取(它在发送所有数据后发生)。当子进程的输出缓冲区被填满时,它会在写入时阻塞并停止从输入读取。现在有两个进程处于死锁状态。
  4. 没有必要使用完全异步的代码。我认为可以起作用的是在写入dot的stdin并在再次写入之前读到dot的stdout的末尾。