ConcurrentDictionary<T,V>
是线程安全的,但并非所有方法都是原子方法。这指出:
...并非所有方法都是原子方法,特别是
GetOrAdd
和AddOrUpdate
。传递给这些方法的用户委托是在字典的内部锁之外调用的。示例问题:
委托方法有可能是对于给定的密钥,该命令执行了多次。对于我们正在使用的大多数实现,这不是理想的行为,并确认了上面MSDN链接中指出的问题。我们想要的是仅执行其中一个委托。
建议的解决方案:
我不得不使用这些方法的委托重载,这导致我不得不研究此问题。进一步。我偶然发现了这篇文章,该文章建议使用
Lazy<T>
类确保该委托仅被调用一次。考虑到这一点,我编写了以下扩展方法来掩盖在值中添加Lazy<T>
包装器。public static readonly ConcurrentDictionary<int, string> store =
new ConcurrentDictionary<int, string>();
[TestMethod]
public void UnsafeConcurrentDictionaryTest()
{
Thread t1 = new Thread(() =>
{
store.GetOrAdd(0, i =>
{
string msg = "Hello from t1";
Trace.WriteLine(msg);
Thread.SpinWait(10000);
return msg;
});
});
Thread t2 = new Thread(() =>
{
store.GetOrAdd(0, i =>
{
string msg = "Hello from t2";
Trace.WriteLine(msg);
Thread.SpinWait(10000);
return msg;
});
});
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
测试解决方案:使用具有惰性值的
ConcurrentDictionary
执行上面的相同测试,将导致仅执行一次值委托(您可以看到“来自t1的Hello”或“来自t2的Hello”)! public static V GetOrAdd<T, U, V>(this ConcurrentDictionary<T, U> dictionary, T key, Func<T, V> valueFactory)
where U : Lazy<V>
{
U lazy = dictionary.GetOrAdd(key, (U)new Lazy<V>(() => valueFactory(key)));
return lazy.Value;
}
public static V AddOrUpdate<T, U, V>(this ConcurrentDictionary<T, U> dictionary, T key, Func<T, V> addValueFactory, Func<T, V, V> updateValueFactory)
where U : Lazy<V>
{
U lazy = dictionary.AddOrUpdate(key,
(U)new Lazy<V>(() => addValueFactory(key)),
(k, oldValue) => (U)new Lazy<V>(() => updateValueFactory(k, oldValue.Value)));
return lazy.Value;
}
看来这种方法可以实现目标。
您如何看待这种方法?
#1 楼
允许调用者提供类型参数
U
意味着允许他们使用Lazy<V>
的子类,但这将不起作用,因为您的实现总是创建一个新的List<V>
并将其转换为U
。由于这意味着U
必须始终是Lazy<V>
,那么为什么不取消使用多余的类型自变量呢?现有方法的名称。为了让使用者使用您的而不是现有的方法,他们将需要通过您的静态类进行访问或使用显式类型参数。当消费者尝试将其用作类型推断的扩展方法时,这可能会导致细微的错误。public static V GetOrAdd<T, V>(this ConcurrentDictionary<T, Lazy<V>> dictionary, T key, Func<T, V> valueFactory)
评论
\ $ \ begingroup \ $
感谢您的来信。关于类型U的要点。而且,我已经被微妙的bug咬住了。我同意我的重载过于类似于现有的GetOrAdd()方法。我会结合您的建议。
\ $ \ endgroup \ $
–亚当·斯派塞(Adam Spicer)
2011年4月23日在3:36
评论
那么这是否意味着如果字典是由第三方公开的,并且未定义为@Daryl我不明白你的问题。这些扩展方法旨在在Dictionary
您提议的解决方案定义为
可能值得注意的是,这种方法不是完全线程安全的,它只是减少了竞争某种条件的机会,但仍然可以发生。考虑一种情况,在这种情况下,第一个线程在分配第一个Lazy实例之后并在第二个Lazy实例之前调用Lazy.Value。下一次对Lazy.Value的调用将再次调用该方法。在非常高性能的应用程序中,这可能是一个真正的问题。