我有以下测试:
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
IDictionary<string, string> dictionary = new ConcurrentDictionary<string, string>(); // FAILS sometimes
// ConcurrentDictionary<string, string> dictionary = new ConcurrentDictionary<string, string>(); // WORKS always
Parallel.For(0, 10, i => dictionary.TryAdd("x1", "y1"));
}
}
如果我在 .NET fiddle 中运行它,有时会出现以下异常:
未处理的异常。 System.AggregateException:发生一个或多个错误。 (该键已存在于字典中)
System.ArgumentException:字典中已存在该键。在 System.Collections.Concurrent.ConcurrentDictionary
2 字典,TKey 键,TValue 值)2.System.Collections.Generic.IDictionary<TKey,TValue>.Add(TKey key, TValue value) at System.Collections.Generic.CollectionExtensions.TryAdd[TKey,TValue](IDictionary
在程序中。<>c__DisplayClass0_0.b__0(Int32 i)
在System.Threading.Tasks.Parallel。<>c__DisplayClass19_02.b__1(RangeWorker&currentWorker,Int64超时,Boolean&replicationDelegateYieldedBeforeCompletion)2.<ForWorker>b__1(RangeWorker& currentWorker, Int64 timeout, Boolean& replicationDelegateYieldedBeforeCompletion) --- End of stack trace from previous location --- at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0
在 System.Threading.Tasks.TaskReplicator.Replica.Execute()
--- 内部异常堆栈跟踪结束 ---
但是,如果我不投射
ConcurrentDictionary
,它似乎总是有效。
发生什么事了?
ConcurrentDictionary.TryAdd 实例方法以原子方式操作。如果使用同一键同时调用此方法两次,则其中一次调用保证返回 false
(而不是抛出异常)。通用 IDictionary 接口没有 TryAdd 实例方法。当您转换为 IDictionary 并调用 TryAdd 时,您实际上是在调用
CollectionExtensions.TryAdd 扩展方法。该方法实现如下:
if (!dictionary.ContainsKey(key))
{
dictionary.Add(key, value);
return true;
}
return false;
如您所见,此方法是不是原子的。两个同时调用 TryAdd 都可以通过 ContainsKey 检查,然后都调用 Add,这会导致异常。
因此,您无法转换为 IDictionary 并保留 ConcurrentDictionary.TryAdd 实例方法的原子行为。