MailboxProcessor:使用return返回内存!在收到之前

ebb*_*ebb 9 f#

给定以下代理,这是一个简单的缓存机制:

type CacheMsg<'a,'b> = Add of 'a * 'b | ForceFlush

type CacheAgent<'a, 'b when 'a : comparison>(size:int, flushCont:Map<'a, 'b> -> unit) =
    let agent = MailboxProcessor.Start(fun inbox ->
        let rec loop (cache : Map<'a, 'b>) = async {
            let inline flush() =
                flushCont cache
                loop Map.empty

            if cache.Count > size then return! flush()

            let! msg = inbox.Receive()

            match msg with
            | Add (key, value) ->
                if cache.ContainsKey key then
                    return! loop cache
                else return! loop (cache.Add(key, value))
            | ForceFlush -> return! flush() }
        loop Map.empty)

    member x.AddIfNotExists key value = Add(key,value) |> agent.Post
    member x.ForceFlush() = agent.Post ForceFlush
Run Code Online (Sandbox Code Playgroud)

此代理将继续占用内存(似乎flushCont在调用时未释放内存).

给出相同的代码,但稍有改动:

type CacheMsg<'a,'b> = Add of 'a * 'b | ForceFlush

type CacheAgent<'a, 'b when 'a : comparison>(size:int, flushCont:Map<'a, 'b> -> unit) =
    let agent = MailboxProcessor.Start(fun inbox ->
        let rec loop (cache : Map<'a, 'b>) = async {
            let inline flush() =
                flushCont cache
                loop Map.empty

            let! msg = inbox.Receive()

            match msg with
            | Add (key, value) ->
                if cache.ContainsKey key then
                    return! loop cache
                else 
                    let newCache = cache.Add(key, value)
                    if newCache.Count > size then 
                        return! flush()
                    else return! loop (cache.Add(key, value))
            | ForceFlush -> return! flush() }
        loop Map.empty)

    member x.AddIfNotExists key value = Add(key,value) |> agent.Post
    member x.ForceFlush() = agent.Post ForceFlush
Run Code Online (Sandbox Code Playgroud)

我已经将决定何时刷新的表达式移动到了union的情况中Add.这导致内存按预期释放.

第一种方法有什么问题,因为它会泄漏内存?

Mar*_*ann 14

第一个版本不是尾递归.

它不是尾递归,因为这个表达式不是函数中的最后一个表达式:

if cache.Count > size then return! flush()
Run Code Online (Sandbox Code Playgroud)

在表达之后,你打电话

let! msg = inbox.Receive()
Run Code Online (Sandbox Code Playgroud)

所以flush()电话不是最后发生的事情.在flush完成隐式的递归调用之后,执行将需要返回到您调用的下一个表达式inbox.Receive().这意味着上下文必须将先前的调用保留在堆栈上,因为递归不在尾部位置:还有更多工作要做.

在第二示例中,所有呼叫flushloop在尾部位置上.

如果你来自C#背景,你会倾向于认为return! flush()退出该功能,但事实并非如此.唯一的理由

if cache.Count > size then return! flush()
Run Code Online (Sandbox Code Playgroud)

甚至编译没有相应的else分支是因为表达式返回unit.这意味着then分支内的代码不会真正退出函数 - 它只是在分支中执行工作(在本例中flush()),然后继续执行后续表达式.