我有几种情况我使用ConcurrentDictionary<TKey, TValue>
来缓存值,但我经常需要对值进行验证,以决定是否使用ConcurrentDictionary<TKey, TValue>.GetOrAdd(TKey, Func<TKey, TValue>)
将其添加到缓存中。
通常沿着以下方式:
private readonly ConcurrentDictionary<Type, ISomeObject> someObjectCache =
new ConcurrentDictionary<Type, ISomeObject>();
public ISomeObject CreateSomeObject(Type someType)
{
return someObjectCache.GetOrAdd(someType, type =>
{
if(!Attribute.IsDefined(someType, typeof(SomeAttribute))
// Do something here to avoid the instance from being added to
// `someObjectCache`
ISomeObject someObject;
// Typical factory functionality goes here
return someObject;
});
}
我今天处理这个问题的方法是抛出一个似乎工作正常的异常,但我想要一个更清晰的方法(可能是我可以设置的标志或我可以设置返回值的特定值)来取消来自lambda中的GetOrAdd
(虽然它可以用一个完整的方法替换它)。
根据我对其他类似LINQ的方法的经验,返回null
会导致值被添加而不会被检查(并且读取GetOrAdd
的IL看起来它会导致同样的问题),所以我认为这不会起作用。
我是否有某种方法可以避免使用例外来取消使用GetOrAdd
添加?
答案 0 :(得分:10)
该页面的相关部分位于底部,引用此处:
此外,尽管ConcurrentDictionary的所有方法(Of TKey,TValue) 是线程安全的,并非所有方法都是原子的,特别是GetOrAdd和 AddOrUpdate。传递给这些方法的用户委托是 在字典的内部锁外部调用。 (这样做是为了 防止未知代码阻塞所有线程。)因此它是 这一系列事件可能发生:
1)threadA调用GetOrAdd,找不到任何项目并创建一个新项目添加 通过调用valueFactory委托。
2)threadB同时调用GetOrAdd,其valueFactory委托是 调用它,它在threadA之前到达内部锁,所以它 新的键值对被添加到字典中。
3)threadA的用户委托完成,线程到达 锁定,但现在看到该项目已存在
4)threadA执行“Get”,并返回先前的数据 由threadB添加。
因此,无法保证返回的数据 GetOrAdd与线程创建的数据相同 valueFactory。 AddOrUpdate可能会发生类似的事件序列 被称为。
我读到这个的方式是,即使你在Add委托中调用了一些锁定,也不能保证你的add返回的值是实际使用的值。
因此,您不需要添加任何进一步的锁定,而是可以使用以下模式:
private ConcurrentDictionary<Type, ISomeObject> someObjectCache =
new ConcurrentDictionary<Type, ISomeObject>();
public ISomeObject CreateSomeObject(Type someType)
{
ISomeObject someObject;
if (someObjectCache.TryGet(someType, out someObject))
{
return someObject;
}
if (Attribute.IsDefined(someType, typeof(SomeAttribute))
{
// init someObject here
someObject = new SomeObject();
return someObjectCache.GetOrAdd(someType, someObject); // If another thread got through here first, we'll return their object here.
}
// fallback functionality goes here if it doesn't have your attribute.
}
是的,这将导致可能多次创建新对象的可能性,但即使调用多个,调用者也将收到相同的结果。与GetOrAdd现在一样。
答案 1 :(得分:3)
计算机科学中的所有问题都可以通过另一层间接来解决
// the dictionary now stores functions
private readonly ConcurrentDictionary<Type, Func<ISomeObject>> someObjectCache =
new ConcurrentDictionary<Type, Func<ISomeObject>>();
public ISomeObject CreateSomeObject(Type someType) {
return someObjectCache.GetOrAdd(someType, _ => {
if(ShouldCache(someType)) {
// caching should be used
// return a function that returns a cached instance
var someObject = Create(someType);
return () => someObject;
}
else {
// no caching should be used
// return a function that always creates a new instance
return () => Create(someType);
}
})(); // call the returned function
}
private bool ShouldCache(Type someType) {
return Attribute.IsDefined(someType, typeof(SomeAttribute));
}
private ISomeObject Create(Type someType) {
// typical factory functionality ...
}
现在存储在字典中的值是函数;当您不希望发生缓存时,该函数始终会创建一个新实例;当你想要缓存时,该函数返回一个缓存的实例。