简体   繁体   English

真正的惰性缓存模式? F#

[英]truly lazy cache pattern? F#

I have the following type for implementing a simple lazy cache: 我有以下类型用于实现简单的惰性缓存:

module CachedFoo =

let mutable private lastAccess:Option<DateTime> = None

// returns a lazy value that initializes the cache when
// accessed for the first time (safely)
let private createCacheInitialization() =
    lazy(
        let someObject = SomeLongRunningOperation()
        lastAccess <- Option.Some(DateTime.Now)
        someObject
    )

// current cache represented as lazy value
let mutable private currentCache = createCacheInitialization()

// Reset - cache will be re-initialized next time it is accessed
// (this doesn't actually initialize a cache - just creates a lazy value)
let MaybeReset() =
    if (lastAccess.IsSome && DateTime.Now > (lastAccess.Value + TimeSpan.FromSeconds (10.0))) then
        currentCache <- createCacheInitialization()

let GetCache() =
    MaybeReset()
    currentCache.Value

First question: is the above thread-safe? 第一个问题:上面的线程安全吗? It seems lazy() is thread-safe by default, but I guess I need to put some locking around the assignment of the lastAccess field? 似乎lazy()默认情况下是线程安全的,但是我想我需要对lastAccess字段的分配进行一些锁定吗?

Second and most important: this is lazy in the sense that its value is not retrieved until someone demands for it, however, I think I could even do it more lazy by returning the last cached object even in the case that Reset() is called, but launching an async thread in the background that would call this method. 第二个也是最重要的一点:这是懒惰的,因为直到有人要求它的值才可以检索它的值,但是,我认为我什至可以通过返回最后一个缓存的对象来更懒惰,即使在调用Reset()的情况下,但在后台启动一个异步线程来调用此方法。

In C# it would be something like this: 在C#中将是这样的:

public SomeObject GetCache() {
    try {
        return currentCache.Value;
    } finally {
        ThreadPool.QueueUserWorkItem(new WaitCallback(MaybeReset));
    }
}

How would I do that in F#? 我将如何在F#中做到这一点? (Bonus points if solution uses fancy async stuff instead of using ThreadPool API). (如果解决方案使用花哨的异步内容而不是使用ThreadPool API,则奖励)。

I think updating lastAccess is thread-safe for two reasons 我认为更新lastAccess是线程安全的,这有两个原因

  • you only do it inside the lazy which means that it's only going to be updated once anyway (though there may be a more subtle race with Reset , I'm not certain) 您只能在lazy执行此操作,这意味着无论如何它只会被更新一次(尽管使用Reset可能会有更细微的竞争,但我不确定)

  • lastAccess is a single reference (to Option ) and so will be updated atomically anyway lastAccess是(对Option )单个引用,因此无论如何都会自动进行原子更新

To kick off a new "fire and forget" async to re-calculate the value, do something like this: 要启动新的“即发即弃” async来重新计算值,请执行以下操作:

let GetCache() =
    let v = currentCache.Value // to make sure we get the old one
    async { MaybeReset() } |> Async.Start
    v

Thanks to Ganesh's insight, I finally went for this solution that doesn't make the 2nd requestor wait on the result while it's being refreshed: 感谢Ganesh的洞察力,我终于选择了这种解决方案,该解决方案不会让第二个请求者在刷新结果时等待结果:

module CachedFoo =

let mutable private lastAccess:Option<DateTime> = None

// returns a lazy value that initializes the cache when
// accessed for the first time (safely)
let private createCacheInitialization() =
    lazy(
        let someObject = SomeLongRunningOperation()
        lastAccess <- Option.Some(DateTime.Now)
        someObject
    )

// current cache represented as lazy value
let mutable private currentCache = createCacheInitialization()

let lockObject = new Object()

let timeout = TimeSpan.FromSeconds (10.0)

// Reset - cache will be re-initialized next time it is accessed
// (this doesn't actually initialize a cache - just creates a lazy value)
let MaybeReset() =
    lock lockObject (fun () ->
        if (lastAccess.IsSome && DateTime.Now > (lastAccess.Value + timeout)) then
            let newCache = createCacheInitialization()
            ignore(newCache.Force())
            currentCache <- newCache
    )

let GetCache() =
    let v = currentCache.Value // to make sure we get the old one
    async { MaybeReset() } |> Async.Start
    v

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM