今天我怀疑List<T>.AddRange
方法与并发集合作为参数一起使用可能不安全,所以我做了一个实验来找出答案:
ConcurrentDictionary<int, int> dictionary = new();
for (int i = 1; i <= 50_000; i++)
dictionary.TryAdd(i, default);
List<KeyValuePair<int, int>> list = new();
Thread thread = new(() =>
{
for (int i = -1; i >= -50_000; i--)
dictionary.TryAdd(i, default);
});
thread.Start();
list.AddRange(dictionary); // Throws
thread.Join();
Console.WriteLine($"dictionary.Count: {dictionary.Count:#,0}, list.Count: {list.Count:#,0}");
ConcurrentDictionary
用50,000个正键初始化.然后在不同的线程上添加50,000个额外的否定键,同时使用AddRange
方法将字典添加到列表中.我预计词典最终将有ConcurrentDictionary
,000个键,列表在50,000到ConcurrentDictionary
,000个项目之间.事实上我得到了ArgumentException
:
Unhandled exception. System.ArgumentException: The index is equal to or greater than the length of the array, or the number of elements in the dictionary is greater than the available space from index to the end of the destination array.
at System.Collections.Concurrent.ConcurrentDictionary`2.System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair`2[] array, Int32 index)
at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection)
at Program.Main()
My question is:为什么会发生这种情况?如何防止这种情况发生?有没有办法确保list.AddRange(dictionary);
号线始终成功,没有例外?
想象一下,这本词典可能是以IEnumerable<T>
的形式送给我的,而我不知道它的潜在类型.这种情况下也会引发相同的异常:
IEnumerable<KeyValuePair<int, int>> enumerable = dictionary;
list.AddRange(enumerable); // Throws
这种行为降低了我对一般使用List<T>.AddRange
API的信心.
Context: this个问题中提到了类似的症状,但没有提供一个最小且可重复的例子,所以我不确定场景是否相同.另一个相关问题是warns8514/calling-tolist-on-concurrentdictionarytkey-tvalue-while-adding-items" title="Calling ToList() on ConcurrentDictionary<TKey, TValue> while adding items">this,关于在ConcurrentDictionary<TKey, TValue>
上呼叫LINQ ToList
.尽管如此,文档warns中关于在并发集合上使用扩展方法,但我没有看到任何针对将并发集合与List<T>.AddRange
方法一起使用的警告.