У меня странное поведение при запуске процессов из 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
.