如何判断`ConcurrentDictionary.GetOrAdd`不添加值?

时间:2012-03-28 03:38:11

标签: c# concurrentdictionary

我有几种情况我使用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添加?

2 个答案:

答案 0 :(得分:10)

据我所读,有no guarantee that the Add factory method will only be called a single time amongst all callers to Get for the same key

该页面的相关部分位于底部,引用此处:

  

此外,尽管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 ...
}

现在存储在字典中的值是函数;当您不希望发生缓存时,该函数始终会创建一个新实例;当你想要缓存时,该函数返回一个缓存的实例。