10

我有几种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;
    });
}

我今天处理这个问题的方法是抛出一个似乎可以正常工作的异常,但我想要一种更简洁的方法(可能是我可以设置的标志或我可以将返回值设置为的特定值)来取消GetOrAdd从lambda(尽管它实际上可以被一个完整的方法取代)。

根据我对其他类似 LINQ 的方法的经验,返回null将导致值被添加而不被检查(并且读取 ILGetOrAdd看起来会导致同样的问题),所以我不认为'会工作的。

有什么方法可以避免使用异常来取消添加使用GetOrAdd

4

2 回答 2

11

根据我的阅读,不能保证 Add 工厂方法只会在所有调用者中调用一次 Get 以获得相同的 key

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

此外,虽然 ConcurrentDictionary(Of TKey, TValue) 的所有方法都是线程安全的,但并非所有方法都是原子的,特别是 GetOrAdd 和 AddOrUpdate。传递给这些方法的用户委托是在字典的内部锁之外调用的。(这样做是为了防止未知代码阻塞所有线程。)因此,可能会发生以下事件序列:

1) threadA 调用 GetOrAdd,没有找到任何项目并通过调用 valueFactory 委托创建一个新项目以添加。

2)threadB同时调用GetOrAdd,调用了它的valueFactory委托,在threadA之前到达了内部锁,因此它的新键值对被添加到字典中。

3)threadA的用户委托完成,线程到达锁,但现在看到item已经存在

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 相同。

于 2012-03-28T03:59:19.603 回答
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 ...
}

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

于 2012-04-08T02:37:03.813 回答