c# - 添加项目时在 ConcurrentDictionary<TKey, TValue> 上调用 ToList()

标签 c# .net linq concurrency concurrentdictionary

我遇到了一个有趣的问题。知道ConcurrentDictionary<TKey, TValue>在修改时可以安全地枚举,(在我的例子中)迭代可能消失或多次出现的元素会产生不必要的副作用,我决定自己创建一个快照,使用 ToList() 。自 ConcurrentDictionary<TKey, TValue>还实现 ICollection<KeyValuePair<TKey, TValue>> ,这会导致List(IEnumerable<T> collection)被使用,这又使用当前项Count以字典的当前大小创建一个数组。 ,然后尝试复制项目 using ICollection<T>.CopyTo(T[] array, int arrayIndex) ,调用其 ConcurrentDictionary<TKey, TValue>实现,最后抛出 ArgumentException如果同时将元素添加到字典中。

全部锁定会扼杀按原样使用集合的意义,因此我的选择似乎是要么继续捕获异常并重试(这绝对不是问题的正确答案),要么实现我自己的ToList() 的版本专门解决这个问题(但话又说回来,简单地增长一个列表,然后可能将其修剪到几个元素的正确大小似乎有点过头了,并且使用 LinkedList 会降低索引性能)。

此外,添加某些在后台创建某种缓冲区的 LINQ 方法(例如 OrderBy )似乎确实可以以牺牲性能为代价来解决问题,但纯粹的 ToList()显然不是,并且当不需要附加功能时,不值得用另一种方法“增强”它。

这可能是任何并发收集的问题吗?

在创建此类快照时,将性能影响降至最低的合理解决方法是什么? (最好是在一些 LINQ 魔法结束时。)

编辑:

经过调查,我可以确认,ToArray() (想想我昨天刚刚经过它)确实解决了快照问题,只要它只是一个简单的快照,当在拍摄所述快照之前需要附加功能(例如过滤、排序)时,它就没有帮助,并且最后仍然需要一个列表/数组。 (在这种情况下,需要额外的调用,重新创建新的集合。)

我没有指出快照可能需要也可能不需要进行这些修改,因此最好在最后进行,所以我会将其添加到问题中。

(另外,如果有人对标题有更好的想法,请告诉我们。)

最佳答案

让我们在这里回答所有并发类型的广泛掩盖问题:

If you split up an operation that deals with the internals in multiple steps, where all the steps must "be in sync", then yes, definitively you will get crashes and odd results due to thread synchronization.

所以如果使用.ToList()首先会询问.Count ,然后调整数组大小,然后使用 foreach获取值并将其放置在列表中,然后,这两个部分肯定有机会获得不同数量的元素。

说实话,我希望其中一些并发类型不要试图通过实现大量接口(interface)来假装它们是正常的集合,但可惜,事实就是如此。

既然您知道了这个问题,您能修复您的代码吗?

是的,您可以,您必须查看类型文档,看看它是否提供了不易出现上述问题的任何形式的快照机制。

结果是ConcurrentDictionary<TKey, TValue>实现.ToArray() ,即documented与:

A new array containing a snapshot of key and value pairs copied from the System.Collections.Concurrent.ConcurrentDictionary.

(我的重点)

怎么样.ToArray()目前已实现?

Using locks ,参见第 697 行。

因此,如果您觉得锁定整个字典来获取快照成本太高,我会质疑从一开始就获取其内容快照的行为。

此外,.GetEnumerator()方法遵循一些相同的规则,来自 documentation :

The enumerator returned from the dictionary is safe to use concurrently with reads and writes to the dictionary, however it does not represent a moment-in-time snapshot of the dictionary. The contents exposed through the enumerator may contain modifications made to the dictionary after GetEnumerator was called.

(再次强调)

那么虽然.GetEnumerator()不会崩溃,它可能不会产生您想要的结果。

根据时间的不同,也可能 .ToArray() ,所以这一切都取决于。

关于c# - 添加项目时在 ConcurrentDictionary<TKey, TValue> 上调用 ToList(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41038514/

相关文章:

c# - 如果声明是接口(interface),编译器无法识别泛型中的属性

c# - 有没有办法在 C# 中为泛型类型的特定版本定义隐式转换运算符?

c# - HttpClient 似乎忽略了 Timeout 属性

c# - 当您在需要 Expression 的地方使用自定义方法时,如何创建 C# 编译错误?

c# - 是否可以序列化一个数组,使其元素不包含在数组的标签中?

c# - .NET 4.0/4.5 WinForms MenuStrip 窃取焦点的奇怪错误

.net - 在单独的进程中运行 .net 代码

c# - Java/.NET 中的 RSA 加密和 .NET 中的解密

C# LINQ 将一个项目附加到数组的末尾

linq - AD0.NET Entity Framework 4.0 或 Linq-to-SQL