Gro*_*ozz 0 f# memory-leaks tail-recursion try-catch agents
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#代理中常见的内存泄漏情况,因为代码非常简单和通用.
解:
事实证明这是一个更大问题的一部分:如果在try/catch块中进行递归调用,函数不能是尾递归的,因为它必须能够在抛出异常时展开堆栈,因此具有保存调用堆栈信息.
更多细节在这里:
正确重写代码(单独的try/catch和return):
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
)