Процесс с перенаправленным стандартным вводом / выводом ведет себя по-разному в зависимости от размера ввода - PullRequest
0 голосов
/ 25 апреля 2018

У меня странное поведение при запуске процессов из F #.Моей основной проблемой было запустить Graphviz dot.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 утверждает, что чтение из StandardOutput и запись дочернего процесса в этот поток в одно и то же время может вызвать взаимоблокировку.Кто такой дочерний процесс в этом случае?Это мой p.StandardInput.Write input?

Другой возможный тупик:

чтение всего текста как из стандартного потока вывода, так и из стандартных потоков ошибок.

НоЯ не читаю поток ошибок.В любом случае, он предлагает обрабатывать ввод / вывод с помощью async, поэтому у меня есть следующая версия:

    // 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.Только асинхронный вход ведет себя корректно по сравнению с обработкой синхронного ввода.Что странно.

Подводя итог.Если у меня есть асинхронная обработка ввода, она работает нормально (за исключением dot.exe, но если я это выясню, возможно, я тоже смогу это исправить), если она имеет обработку синхронного ввода, это в зависимости от размеравход.(300 работает, 3000 нет) Почему это так?

Обновление

Поскольку мне действительно не нужно было перенаправлять стандартную ошибку, я удалил RedirectStandardError = true.Это решило загадочную проблему dot.exe.

1 Ответ

0 голосов
/ 25 апреля 2018

Я думаю, что тупик здесь следующий:

  1. Процесс хоста записывает слишком много данных во входной буфер.
  2. Дочерний процесс читает из буфера и записывает в вывод.
  3. Процесс хоста не читает из буфера (это происходит после отправки всех данных). Когда выходной буфер дочернего процесса заполнен, он блокируется при записи и прекращает чтение из ввода. Два процесса теперь находятся в тупиковом состоянии.

Нет необходимости использовать полностью асинхронный код. Я думаю, что сработает запись кусками в стандартный поток точек и чтение до конца стандартного вывода точки перед повторной записью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...