我有以下类型来实现一个简单的延迟缓存:
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
第一个问题:上面的线程安全吗?看来lazy()默认是线程安全的,但我想我需要对lastAccess
字段的赋值进行一些锁定?
第二个也是最重要的:这是懒惰的,因为它的值在有人要求它之前不会被检索,但是,我认为即使在重置的情况下,我甚至可以通过返回最后一个缓存对象来做得更懒。 )被调用,但在后台启动一个异步线程来调用这个方法。
在C#中它会是这样的:
public SomeObject GetCache() {
try {
return currentCache.Value;
} finally {
ThreadPool.QueueUserWorkItem(new WaitCallback(MaybeReset));
}
}
我如何在F#中做到这一点? (如果解决方案使用花哨的异步内容而不是使用ThreadPool API,则会获得奖励。)
答案 0 :(得分:1)
我认为更新lastAccess
是线程安全的,原因有两个
你只能在lazy
内进行,这意味着它只会被更新一次(尽管Reset
可能会有更微妙的比赛,我和# 39;我不确定)
lastAccess
是一个引用(Option
),因此无论如何都会以原子方式更新
开始新的火灾并忘记&#34; async
要重新计算价值,请执行以下操作:
let GetCache() =
let v = currentCache.Value // to make sure we get the old one
async { MaybeReset() } |> Async.Start
v
答案 1 :(得分:0)
感谢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