【问题标题】:Calling ToList() on ConcurrentDictionary<TKey, TValue> while adding items添加项目时在 ConcurrentDictionary<TKey, TValue> 上调用 ToList()
【发布时间】:2016-12-08 11:35:05
【问题描述】:

我遇到了一个有趣的问题。知道ConcurrentDictionary&lt;TKey, TValue&gt; 在被修改时是可以安全枚举的,并且(在我的情况下)迭代可能多次消失或出现的元素会产生不必要的副作用,我决定自己创建一个快照,使用ToList()。由于ConcurrentDictionary&lt;TKey, TValue&gt; 也实现了ICollection&lt;KeyValuePair&lt;TKey, TValue&gt;&gt;,这将导致List(IEnumerable&lt;T&gt; collection) 被使用,这反过来又使用当前项Count 在字典的当前大小中创建一个数组,然后尝试复制项@987654327 @,调用其ConcurrentDictionary&lt;TKey, TValue&gt; 实现,如果同时将元素添加到字典中,则最后抛出ArgumentException。

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

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

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

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

编辑:

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

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

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

【问题讨论】:

  • 请改用.ToArray(),具体由类型实现。

标签: .net linq concurrentdictionary argumentexception


【解决方案1】:

让我们在这里回答所有并发类型的笼统问题:

如果您将处理内部结构的操作拆分为多个步骤,其中所有步骤必须“同步”,那么是的,您肯定会因为线程而导致崩溃和奇怪的结果同步。

所以如果使用.ToList() 将首先要求.Count,然后调整数组大小,然后使用foreach 获取值并放入列表中,然后是,确定你这两个部分有可能获得不同数量的元素。

说实话,我希望那些并发类型中的一些不要试图通过实现很多这样的接口来假装它们是正常的集合,但可惜,事实就是这样。

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

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

原来ConcurrentDictionary&lt;TKey, TValue&gt; 实现了.ToArray(),即documented:

一个包含从 System.Collections.Concurrent.ConcurrentDictionary 复制的键和值对的快照的新数组。

(我的重点)

.ToArray() 目前是如何实现的?

Using locks,见第 697 行。

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

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

从字典返回的枚举器可以安全地同时使用来读取和写入字典,但它并不代表字典的即时快照。 通过枚举器公开的内容可能包含调用 GetEnumerator 后对字典所做的修改。

(再次强调一下)

所以虽然.GetEnumerator() 不会崩溃,但它可能不会产生你想要的结果。

视时间而定,.ToArray() 也不可能,所以这一切都取决于。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-25
    • 1970-01-01
    • 2018-05-17
    • 1970-01-01
    • 2016-06-14
    相关资源
    最近更新 更多