问题描述
我使用并发字典作为线程安全的静态缓存并注意到以下行为:
I am using a concurrent dictionary as a thread-safe static cache and noticed the following behavior:
来自 MSDN 上的文档获取或添加:
如果您在不同的线程上同时调用 GetOrAdd,addValueFactory 可能会被多次调用,但它的键/值对可能不会每次调用都添加到字典中.
我希望能够保证工厂只被调用一次.有没有什么方法可以使用 ConcurrentDictionary API 做到这一点,而无需诉诸我自己的单独同步(例如,在 valueFactory 中锁定)?
我的用例是 valueFactory 在动态模块内生成类型,所以如果同一键的两个 valueFactories 同时运行,我点击:
My use case is that valueFactory is generating types inside a dynamic module so if two valueFactories for the same key run concurrently I hit:
System.ArgumentException:程序集中的类型名称重复.
推荐答案
你可以使用这样的字典:ConcurrentDictionary>
,然后你的值工厂将返回一个 Lazy
对象,该对象已用 LazyThreadSafetyMode.ExecutionAndPublication
初始化,这是 Lazy
使用的默认选项> 如果你不指定它.通过指定 LazyThreadSafetyMode.ExecutionAndPublication
,您告诉 Lazy 只有一个线程可以初始化并设置对象的值.
You could use a dictionary that is typed like this: ConcurrentDictionary<TKey, Lazy<TValue>>
, and then the your value factory would return a Lazy<TValue>
object that has been initialized with LazyThreadSafetyMode.ExecutionAndPublication
, which is the default option used by Lazy<TValue>
if you don't specify it. By specifying the LazyThreadSafetyMode.ExecutionAndPublication
you are telling Lazy only one thread may initialize and set the value of the object.
这导致 ConcurrentDictionary
只使用 Lazy
对象的一个实例,而 Lazy
对象保护了更多超过一个线程初始化其值.
This results in the ConcurrentDictionary
only using one instance of the Lazy<TValue>
object, and the Lazy<TValue>
object protects more than one thread from initializing its value.
即
var dict = new ConcurrentDictionary<int, Lazy<Foo>>();
dict.GetOrAdd(key,
(k) => new Lazy<Foo>(valueFactory)
);
缺点是每次访问字典中的对象时都需要调用 *.Value .这里有一些 扩展 可以帮助解决这个问题.
The downside then is you'll need to call *.Value every time you are accessing an object in the dictionary. Here are some extensions that'll help with that.
public static class ConcurrentDictionaryExtensions
{
public static TValue GetOrAdd<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, Func<TKey, TValue> valueFactory
)
{
return @this.GetOrAdd(key,
(k) => new Lazy<TValue>(() => valueFactory(k))
).Value;
}
public static TValue AddOrUpdate<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, Func<TKey, TValue> addValueFactory,
Func<TKey, TValue, TValue> updateValueFactory
)
{
return @this.AddOrUpdate(key,
(k) => new Lazy<TValue>(() => addValueFactory(k)),
(k, currentValue) => new Lazy<TValue>(
() => updateValueFactory(k, currentValue.Value)
)
).Value;
}
public static bool TryGetValue<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, out TValue value
)
{
value = default(TValue);
var result = @this.TryGetValue(key, out Lazy<TValue> v);
if (result) value = v.Value;
return result;
}
// this overload may not make sense to use when you want to avoid
// the construction of the value when it isn't needed
public static bool TryAdd<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, TValue value
)
{
return @this.TryAdd(key, new Lazy<TValue>(() => value));
}
public static bool TryAdd<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, Func<TKey, TValue> valueFactory
)
{
return @this.TryAdd(key,
new Lazy<TValue>(() => valueFactory(key))
);
}
public static bool TryRemove<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, out TValue value
)
{
value = default(TValue);
if (@this.TryRemove(key, out Lazy<TValue> v))
{
value = v.Value;
return true;
}
return false;
}
public static bool TryUpdate<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, Func<TKey, TValue, TValue> updateValueFactory
)
{
if ([email protected](key, out Lazy<TValue> existingValue))
return false;
return @this.TryUpdate(key,
new Lazy<TValue>(
() => updateValueFactory(key, existingValue.Value)
),
existingValue
);
}
}
这篇关于为什么 ConcurrentDictionary.GetOrAdd(key, valueFactory) 允许 valueFactory 被调用两次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!