问题描述
我使用并发字典作为线程安全的静态缓存,并注意到以下行为:
I am using a concurrent dictionary as a thread-safe static cache and noticed the following behavior:
从:
我想能够保证工厂只被调用一次。有没有办法这样做与ConcurrentDictionary API,而不求助于我自己的单独的同步(例如锁定在内部valueFactory)?
我的用例是valueFactory是在一个动态模块内部生成类型,所以如果同一个键的两个valueFactory同时运行我命中:
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:
推荐答案
您可以使用类型如下的字典: ConcurrentDictionary< TKey,Lazy< TValue>>
,然后您的值工厂将返回一个 Lazy< TValue>
已使用 LazyThreadSafetyMode.ExecutionAndPublication
初始化。通过指定 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
. By specifying the LazyThreadSafetyMode.ExecutionAndPublication
you are telling Lazy only one thread may initialize and set the value of the object.
这导致 ConcurrentDictionary
只使用 Lazy< TValue>
对象的一个实例, c> Lazy< TValue> 对象保护多个线程初始化其值。
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.
ie
var dict = new ConcurrentDictionary<int, Lazy<Foo>>();
dict.GetOrAdd(
key,
(k) => new Lazy<Foo>(
valueFactory,
LazyThreadSafetyMode.ExecutionAndPublication
));
这样做的缺点是每次访问对象时都需要调用* .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),
LazyThreadSafetyMode.ExecutionAndPublication
)).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),
LazyThreadSafetyMode.ExecutionAndPublication
),
(k, currentValue) => new Lazy<TValue>(
() => updateValueFactory(k, currentValue.Value),
LazyThreadSafetyMode.ExecutionAndPublication
)
).Value;
}
public static bool TryGetValue<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, out TValue value
)
{
value = default(TValue);
Lazy<TValue> v;
var result = @this.TryGetValue(key, out 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),
LazyThreadSafetyMode.ExecutionAndPublication
)
);
}
public static bool TryRemove<TKey, TValue>(
this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
TKey key, out TValue value
)
{
value = default(TValue);
Lazy<TValue> v;
if (@this.TryRemove(key, out 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
)
{
Lazy<TValue> existingValue;
if([email protected](key, out existingValue))
return false;
return @this.TryUpdate(
key,
new Lazy<TValue>(
() => updateValueFactory(key, existingValue.Value),
LazyThreadSafetyMode.ExecutionAndPublication
),
existingValue
);
}
}
这篇关于为什么ConcurrentDictionary.GetOrAdd(key,valueFactory)允许valueFactory被调用两次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!