对于有条件的添加要求,我总是使用ConcurrentDictionary
,它有一个重载GetOrAdd
方法,如果需要构建对象,它接受一个委托来触发。
ConcurrentDictionary<string, object> _cache = new
ConcurrenctDictionary<string, object>();
public void GetOrAdd(string key)
{
return _cache.GetOrAdd(key, (k) => {
//here 'k' is actually the same as 'key'
return buildDataUsingGoodAmountOfResources();
});
}
实际上,我几乎总是使用static
并发字典。我曾经有一个受ReaderWriterLockSlim
实例保护的“普通”字典,但是一旦我切换到 .Net 4(它只从那以后可用),我就开始转换我遇到的任何字典。
ConcurrentDictionary
至少可以说,他的表现令人钦佩:)
使用仅基于年龄的过期语义更新Naive 实现。还应确保单个项目仅创建一次 - 根据@usr 的建议。 再次更新- 正如@usr 所建议的那样 - 简单地使用 aLazy<T>
会简单得多 - 您可以在将创建委托添加到并发字典时将其转发给该委托。我更改了代码,因为实际上我的锁字典无论如何都不会起作用。但我自己真的应该想到这一点(尽管在英国的午夜过后,我被打败了。任何同情?不,当然不是。作为一名开发人员,我有足够的咖啡因在我的血管中流淌来唤醒死者)。
不过,我确实建议IRegisteredObject
用这个实现接口,然后用HostingEnvironment.RegisterObject
方法注册它——这样做会提供一种更简洁的方法来在应用程序池关闭/回收时关闭轮询线程。
public class ConcurrentCache : IDisposable
{
private readonly ConcurrentDictionary<string, Tuple<DateTime?, Lazy<object>>> _cache =
new ConcurrentDictionary<string, Tuple<DateTime?, Lazy<object>>>();
private readonly Thread ExpireThread = new Thread(ExpireMonitor);
public ConcurrentCache(){
ExpireThread.Start();
}
public void Dispose()
{
//yeah, nasty, but this is a 'naive' implementation :)
ExpireThread.Abort();
}
public void ExpireMonitor()
{
while(true)
{
Thread.Sleep(1000);
DateTime expireTime = DateTime.Now;
var toExpire = _cache.Where(kvp => kvp.First != null &&
kvp.Item1.Value < expireTime).Select(kvp => kvp.Key).ToArray();
Tuple<string, Lazy<object>> removed;
object removedLock;
foreach(var key in toExpire)
{
_cache.TryRemove(key, out removed);
}
}
}
public object CacheOrAdd(string key, Func<string, object> factory,
TimeSpan? expiry)
{
return _cache.GetOrAdd(key, (k) => {
//get or create a new object instance to use
//as the lock for the user code
//here 'k' is actually the same as 'key'
return Tuple.Create(
expiry.HasValue ? DateTime.Now + expiry.Value : (DateTime?)null,
new Lazy<object>(() => factory(k)));
}).Item2.Value;
}
}