在John Palmer在评论中指出明显错误后更新了。
以下代码会产生OutOfMemoryException
:
let agent = MailboxProcessor<string>.Start(fun agent ->
let maxLength = 1000
let rec loop (state: string list) i = async {
let! msg = agent.Receive()
try
printfn "received message: %s, iteration: %i, length: %i" msg i state.Length
let newState = state |> Seq.truncate maxLength |> Seq.toList
return! loop (msg::newState) (i+1)
with
| ex ->
printfn "%A" ex
return! loop state (i+1)
}
loop [] 0
)
let greeting = "hello"
while true do
agent.Post greeting
System.Threading.Thread.Sleep(1) // avoid piling up greetings before they are output
如果我不使用try / catch块,错误就会消失。
增加睡眠时间只会推迟错误。
更新2:我想这里的问题是该函数不再是递归调用,因为递归调用不再是最后一个执行调用。对于那些有更多F#经验的人来说,对它来说是很好的,因为我确信这是F#代理中常见的内存泄漏情况,因为代码非常简单和通用。
答案 0 :(得分:7)
<强>解决方案:强>
事实证明这是一个更大问题的一部分:如果在try / catch块中进行递归调用,函数不能是尾递归的,因为它必须能够展开堆栈,如果抛出异常,因此必须保存调用堆栈信息。
此处有更多详情:
Tail recursion and exceptions in F#
正确重写代码(单独的try / catch并返回):
let agent = MailboxProcessor<string>.Start(fun agent ->
let maxLength = 1000
let rec loop (state: string list) i = async {
let! msg = agent.Receive()
let newState =
try
printfn "received message: %s, iteration: %i, length: %i" msg i state.Length
let truncatedState = state |> Seq.truncate maxLength |> Seq.toList
msg::truncatedState
with
| ex ->
printfn "%A" ex
state
return! loop newState (i+1)
}
loop [] 0
)
答案 1 :(得分:2)
我怀疑问题实际上在这里:
while true do
agent.Post "hello"
你发布的所有"hello"
都必须存储在内存中,并且推送速度比printf
答案 2 :(得分:2)
请在此处查看我的旧帖子http://vaskir.blogspot.ru/2013/02/recursion-and-trywithfinally-blocks.html
答案 3 :(得分:0)
基本上在返回后执行的任何操作(如try / with / finally / dispose)都会阻止尾调用。
请参阅https://blogs.msdn.microsoft.com/fsharpteam/2011/07/08/tail-calls-in-f/
还有一些工作要让编译器警告缺少尾递归:https://github.com/fsharp/fslang-design/issues/82