我将并发字典用作线程安全的静态缓存,并注意到以下行为:
从the MSDN docs on GetOrAdd:
我希望能够保证工厂仅被调用一次。有什么方法可以使用ConcurrentDictionary API做到这一点,而无需依靠我自己的单独同步功能(例如,将其锁定在valueFactory内)?
我的用例是valueFactory在动态模块内生成类型,因此如果同一键的两个valueFactories同时运行,我会命中:System.ArgumentException: Duplicate type name within an assembly.
最佳答案
您可以使用类型如下的字典:ConcurrentDictionary<TKey, Lazy<TValue>>
,然后您的值工厂将返回一个已使用Lazy<TValue>
初始化的LazyThreadSafetyMode.ExecutionAndPublication
对象,如果未指定,则该默认选项是Lazy<TValue>
使用的默认选项。通过指定LazyThreadSafetyMode.ExecutionAndPublication
,您告诉Lazy只有一个线程可以初始化和设置对象的值。
这将导致ConcurrentDictionary
仅使用Lazy<TValue>
对象的一个实例,并且Lazy<TValue>
对象可防止多个线程初始化其值。
IE。
var dict = new ConcurrentDictionary<int, Lazy<Foo>>();
dict.GetOrAdd(key,
(k) => new Lazy<Foo>(valueFactory)
);
不利的一面是,每次访问字典中的对象时,您都需要调用* .Value。这里有一些extensions可以帮助您。
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
);
}
}
关于c# - 为什么ConcurrentDictionary.GetOrAdd(key,valueFactory)允许valueFactory被调用两次?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12611167/