Cha*_*ion 26 c# concurrency multithreading caching concurrentdictionary
我使用并发字典作为线程安全的静态缓存,并注意到以下行为:
如果在不同的线程上同时调用GetOrAdd,可能会多次调用addValueFactory,但是对于每个调用,它的键/值对可能不会添加到字典中.
我希望能够保证工厂只被召唤一次.有没有办法使用ConcurrentDictionary API执行此操作而不依赖于我自己的单独同步(例如锁定valueFactory)?
我的用例是valueFactory在动态模块中生成类型,所以如果同一个键的两个valueFactories同时运行,我点击:
System.ArgumentException: Duplicate type name within an assembly.
JG *_* SD 38
您可以使用这样输入的字典:ConcurrentDictionary<TKey, Lazy<TValue>>
,然后您的值工厂将返回一个Lazy<TValue>
已初始化的对象,如果您未指定它LazyThreadSafetyMode.ExecutionAndPublication
,Lazy<TValue>
则使用该默认选项.通过指定LazyThreadSafetyMode.ExecutionAndPublication
您告诉Lazy,只有一个线程可以初始化并设置对象的值.
这导致ConcurrentDictionary
仅使用Lazy<TValue>
对象的一个实例,并且该Lazy<TValue>
对象保护多个线程不初始化其值.
即
var dict = new ConcurrentDictionary<int, Lazy<Foo>>();
dict.GetOrAdd(key,
(k) => new Lazy<Foo>(valueFactory)
);
Run Code Online (Sandbox Code Playgroud)
不利的一面是,每次访问字典中的对象时,都需要调用*.Value.以下是一些有助于此的扩展.
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 (!@this.TryGetValue(key, out Lazy<TValue> existingValue))
return false;
return @this.TryUpdate(key,
new Lazy<TValue>(
() => updateValueFactory(key, existingValue.Value)
),
existingValue
);
}
}
Run Code Online (Sandbox Code Playgroud)
这种情况对于非阻塞算法并不罕见.他们基本上测试确认没有争用的条件Interlock.CompareExchange
.它们循环,直到CAS成功.看一下ConcurrentQueue
第(4)页作为非阻塞算法的一个很好的介绍
简短的回答是否定的,它是野兽的本质,它需要多次尝试添加到争用的集合中.除了使用传递值的其他重载之外,您还需要防止值工厂内的多次调用,可能使用双锁/内存屏障.