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

时间:2014-05-20 23:56:13

标签: 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

此代理将继续占用内存(似乎在调用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

我已将决定何时刷新的表达式移动到联合案例Add中。这导致内存按预期释放。

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

1 个答案:

答案 0 :(得分:12)

第一个版本不是tail recursive

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

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

在表达式之后,您调用

let! msg = inbox.Receive()

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

在第二个示例中,对flushloop的所有来电都处于尾部位置。

如果你来自C#背景,你会倾向于认为return! flush()退出了这个功能,但事实并非如此。唯一的原因

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

即使在没有相应else分支的情况下编译也是because the expression returns unit。这意味着then分支内的代码并不真正退出函数 - 它只是在分支中执行工作(在本例中为flush()),然后继续执行后续表达式。