我有一个关于从 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'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
哪里outTxt
是 File.Create
不是 File.CreateText
.只有异步输入对同步输入处理的行为是正确的。这很奇怪。把它们加起来。如果我有异步输入处理,它工作正常(除了
dot.exe
,但如果我弄清楚了,也许我也可以解决这个问题),如果它有同步输入处理,它是 取决于 关于输入的大小。 (300 个有效,3000 个无效)为什么会这样?更新
因为我真的不需要重定向标准错误,所以我删除了
RedirectStandardError = true
.那解决了神秘dot.exe
问题。
最佳答案
我认为这里的僵局如下:
没有必要使用完全异步的代码。我认为可行的是将块写入点的标准输入并在再次写入之前读取到点的标准输出的末尾。
关于具有重定向标准输入/输出的进程的行为取决于输入的大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50026915/