问题:我需要实现对象缓存。缓存需要是线程安全的,需要按需填充值(延迟加载)。通过密钥(缓慢操作)通过Web服务检索值。所以我决定使用ConcurrentDictionary
及其GetOrAdd()方法,该方法具有值工厂方法,假设操作是原子的并且是同步的。不幸的是,我在MSDN文章中找到了以下声明:How to: Add and Remove Items from a ConcurrentDictionary:
此外,尽管ConcurrentDictionary的所有方法都是 线程安全,并非所有方法都是原子的,特别是GetOrAdd和 AddOrUpdate。传递给这些方法的用户委托是 在字典的内部锁外部调用。
那很不幸但仍然没有完全回答我的答案。
问题:每个键只调用一次工厂值吗?在我的具体情况中:是否有可能寻找相同密钥的多个线程向Web服务产生多个请求以获得相同的值?
答案 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;
中进一步说明
答案 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
不会被多次调用。