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

M.B*_*ock 10 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;
    });
}
Run Code Online (Sandbox Code Playgroud)

我今天这个处理的方式是抛出这似乎做工精细异常,但我想一个更简洁的方法(可能是一个标志,我可以设置或一个特定的值,我可以设置返回值)来取消GetOrAdd从内lambda(虽然它可以实际上被完整的方法取代).

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

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

小智 10

从我读过的内容来看,无法保证Add factory方法只会在所有调用者中被调用一次以获取相同的密钥.

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

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

1)threadA调用GetOrAdd,找不到任何项,并通过调用valueFactory委托创建一个新项添加到Add.

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. 
}
Run Code Online (Sandbox Code Playgroud)

是的,这将导致可能多次创建新对象的可能性,但即使调用多个,调用者也将收到相同的结果.与GetOrAdd现在一样.