具有重定向标准输入/输出的进程的行为取决于输入的大小

标签 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命名 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() .除非我将输入音量更改为 Seq.replicate 300 "aaaa" .为什么它的工作方式不同?

Process.StandardOutput's reference声明从 StandardOutput 读取和子进程同时写入该流可能会导致死锁。在这种情况下,谁是子进程?是我的吗p.StandardInput.Write input ?

其他可能的僵局是

read all text from both the standard output and standard error streams.



但我没有阅读错误流。无论如何,它建议使用异步处理输入/输出,因此我有以下版本:
    // 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哪里outTxtFile.Create不是 File.CreateText .只有异步输入对同步输入处理的行为是正确的。这很奇怪。

把它们加起来。如果我有异步输入处理,它工作正常(除了 dot.exe ,但如果我弄清楚了,也许我也可以解决这个问题),如果它有同步输入处理,它是 取决于 关于输入的大小。 (300 个有效,3000 个无效)为什么会这样?

更新

因为我真的不需要重定向标准错误,所以我删除了 RedirectStandardError = true .那解决了神秘dot.exe问题。

最佳答案

我认为这里的僵局如下:

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

  • 没有必要使用完全异步的代码。我认为可行的是将块写入点的标准输入并在再次写入之前读取到点的标准输出的末尾。

    关于具有重定向标准输入/输出的进程的行为取决于输入的大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50026915/

    相关文章:

    powershell - 在 PowerShell 中使用 Plink 创建进程并向其发送命令

    python multiprocessing BaseManager注册类在Ctrl-C后立即失去连接

    visual-studio - 在 VB(或 C# 或其他)中调用 F# 函数

    c# - F# 类型到 Json 正在输出 Name@ 和 Name

    f#:常量联合案例标记号

    dart - Dart Process.kill无效

    实际应用中等待所有 child 的正确方法

    java - 如何更改 Java 应用程序进程的名称?

    f# - Async.BuildPrimitive 和 Async.Spawn 在哪里定义?

    class - 使用 F# 在显式对象构造函数中失败