ConcurrentDictionary.GetOrAdd()是否保证每个键仅调用一次valueFactoryMethod?

时间:2015-07-26 13:16:09

标签: c# .net multithreading parallel-processing

问题:我需要实现对象缓存。缓存需要是线程安全的,需要按需填充值(延迟加载)。通过密钥(缓慢操作)通过Web服务检索值。所以我决定使用ConcurrentDictionary及其GetOrAdd()方法,该方法具有值工厂方法,假设操作是原子的并且是同步的。不幸的是,我在MSDN文章中找到了以下声明:How to: Add and Remove Items from a ConcurrentDictionary

  

此外,尽管ConcurrentDictionary的所有方法都是   线程安全,并非所有方法都是原子的,特别是GetOrAdd和   AddOrUpdate。传递给这些方法的用户委托是   在字典的内部锁外部调用。

那很不幸但仍然没有完全回答我的答案。

问题:每个键只调用一次工厂值吗?在我的具体情况中:是否有可能寻找相同密钥的多个线程向Web服务产生多个请求以获得相同的值?

3 个答案:

答案 0 :(得分:27)

正如其他人已经指出的那样,valueFactory可能会被多次调用。有一个通用的解决方案可以缓解此问题 - 让您的valueFactory返回Lazy<T>个实例。虽然可能会创建多个惰性实例,但只有在您访问T属性时才会创建实际Lazy<T>.Value值。

具体做法是:

// Lazy instance may be created multiple times, but only one will actually be used.
// GetObjectFromRemoteServer will not be called here.
var lazyObject = dict.GetOrAdd("key", key => new Lazy<MyObject>(() => GetObjectFromRemoteServer()));

// Only here GetObjectFromRemoteServer() will be called.
// The next calls will not go to the server
var myObject = lazyObject.Value;

此方法在Reed Copsey's blog post

中进一步说明

答案 1 :(得分:19)

  

每个键只调用一次值工厂吗?

不,不是。 EAFP mantra

  

如果您在不同的主题上同时拨打GetOrAdd,    valueFactory可以多次调用   多次调用,但可能无法添加其键/值对   每个电话的字典。

答案 2 :(得分:8)

我们来看看RoboBrowser

public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
{
    if (key == null) throw new ArgumentNullException("key");
    if (valueFactory == null) throw new ArgumentNullException("valueFactory");

    TValue resultingValue;
    if (TryGetValue(key, out resultingValue))
    {
        return resultingValue;
    }
    TryAddInternal(key, valueFactory(key), false, true, out resultingValue);
    return resultingValue;
}

不幸的是,在这种情况下,如果两个valueFactory调用碰巧并行运行,很明显没有任何保证GetOrAdd不会被多次调用。