ConcurrentDictionary.GetOrAdd() 是否保证每个键只调用一次 valueFactoryMethod?
Is ConcurrentDictionary.GetOrAdd() guaranteed to invoke valueFactoryMethod only once per key?
问题:我需要实现对象缓存。缓存需要是线程安全的并且需要按需填充值(延迟加载)。这些值是通过 Key 的 Web 服务检索的(操作缓慢)。所以我决定使用 ConcurrentDictionary
及其 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:
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.
嗯,这很不幸,但仍然没有完全回答我的答案。
问题:每个键只调用一次值工厂吗?在我的具体情况下:是否有可能正在寻找相同密钥的多个线程针对相同的值对 Web 服务产生多个请求?
我们来看看source code of 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;
}
不幸的是,在这种情况下,如果两个 GetOrAdd
调用同时发生在 运行 上,显然没有任何东西可以保证 valueFactory
不会被调用多次。
Is value factory invoked only once per key?
不,不是。 The docs say:
If you call GetOrAdd
simultaneously on different threads,
valueFactory may be invoked multiple times, but its key/value pair might not be added to
the dictionary for every call.
正如其他人已经指出的那样,valueFactory
可能会被多次调用。有一个缓解此问题的通用解决方案 - 让您的 valueFactory
return 成为 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;
中有进一步说明
问题:我需要实现对象缓存。缓存需要是线程安全的并且需要按需填充值(延迟加载)。这些值是通过 Key 的 Web 服务检索的(操作缓慢)。所以我决定使用 ConcurrentDictionary
及其 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:
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.
嗯,这很不幸,但仍然没有完全回答我的答案。
问题:每个键只调用一次值工厂吗?在我的具体情况下:是否有可能正在寻找相同密钥的多个线程针对相同的值对 Web 服务产生多个请求?
我们来看看source code of 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;
}
不幸的是,在这种情况下,如果两个 GetOrAdd
调用同时发生在 运行 上,显然没有任何东西可以保证 valueFactory
不会被调用多次。
Is value factory invoked only once per key?
不,不是。 The docs say:
If you call
GetOrAdd
simultaneously on different threads, valueFactory may be invoked multiple times, but its key/value pair might not be added to the dictionary for every call.
正如其他人已经指出的那样,valueFactory
可能会被多次调用。有一个缓解此问题的通用解决方案 - 让您的 valueFactory
return 成为 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;
中有进一步说明