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

时间:2022-03-25 13:53:45

Problem: I need to implement object cache. The cache need to be thread-safe and need to populate values on demand(lazy loading). The values are retrieved via web service by Key(slow operation). So I've decided to use ConcurrentDictionary and its GetOrAdd() method that has a value factory method supposing that the operation is atomic and synchronized. Unfortunately I found the following statement in the MSDN article: How to: Add and Remove Items from a ConcurrentDictionary:

问题:我需要实现对象缓存。缓存需要是线程安全的,需要按需填充值(延迟加载)。通过Web的Web服务检索值(慢速操作)。所以我决定使用ConcurrentDictionary及其GetOrAdd()方法,该方法具有一个值工厂方法,假设该操作是原子的并且是同步的。不幸的是我在MSDN文章中找到了以下语句:如何:在ConcurrentDictionary中添加和删除项:

Also, although all methods of ConcurrentDictionary are thread-safe, not all methods are atomic, specifically GetOrAdd and AddOrUpdate. The user delegate that is passed to these methods is invoked outside of the dictionary's internal lock.

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

Well that's unfortunate but still doesn't answer my answer completely.

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

Question: Is value factory invoked only once per key? In my specific case: Is it possible that multiple threads that are looking for the same key spawning multiple request to the web service for the same value?

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

3 个解决方案

#1


14  

Is value factory invoked only once per key?

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

No, it isn't. The docs say:

不,不是。文档说:

If you call GetOrAdd simultaneously on different threads, valueFactory may be invoked multiple times called multiple times, but its key/value pair might not be added to the dictionary for every call.

如果在不同的线程上同时调用GetOrAdd,则可以多次调用valueFactory多次,但是对于每次调用,它的键/值对可能不会被添加到字典中。

#2


21  

As others have already pointed out, valueFactory may be invoked more than once. There is a common solution that mitigates this issue - have your valueFactory return a Lazy<T> instance. Although it's possible that multiple lazy instances will be created, the actual T value will only be created when you access Lazy<T>.Value property.

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

Specifically:

// 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;

This method is further explained in Reed Copsey's blog post

在Reed Copsey的博客文章中进一步解释了这种方法

#3


4  

Let's take a look at the source code of GetOrAdd:

我们来看看GetOrAdd的源代码:

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;
}

Unfortunately, in this case, it's clear nothing guarantees that valueFactory won't be called more than once, if two GetOrAdd invocations happen to run in parallel.

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

#1


14  

Is value factory invoked only once per key?

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

No, it isn't. The docs say:

不,不是。文档说:

If you call GetOrAdd simultaneously on different threads, valueFactory may be invoked multiple times called multiple times, but its key/value pair might not be added to the dictionary for every call.

如果在不同的线程上同时调用GetOrAdd,则可以多次调用valueFactory多次,但是对于每次调用,它的键/值对可能不会被添加到字典中。

#2


21  

As others have already pointed out, valueFactory may be invoked more than once. There is a common solution that mitigates this issue - have your valueFactory return a Lazy<T> instance. Although it's possible that multiple lazy instances will be created, the actual T value will only be created when you access Lazy<T>.Value property.

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

Specifically:

// 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;

This method is further explained in Reed Copsey's blog post

在Reed Copsey的博客文章中进一步解释了这种方法

#3


4  

Let's take a look at the source code of GetOrAdd:

我们来看看GetOrAdd的源代码:

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;
}

Unfortunately, in this case, it's clear nothing guarantees that valueFactory won't be called more than once, if two GetOrAdd invocations happen to run in parallel.

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