在ConcurrentDictionary< TKey,TValue>上调用ToList().在添加项目时 [英] Calling ToList() on ConcurrentDictionary<TKey, TValue> while adding items

查看:237
本文介绍了在ConcurrentDictionary< TKey,TValue>上调用ToList().在添加项目时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一个有趣的问题.知道ConcurrentDictionary<TKey, TValue>在修改时可以安全地枚举,并且(对于我而言)具有遍历可能消失或多次出现的元素的不必要的副作用,因此,我决定使用ToList()自己创建快照.由于ConcurrentDictionary<TKey, TValue>也实现了ICollection<KeyValuePair<TKey, TValue>>,因此将导致使用List(IEnumerable<T> collection),这又将使用当前项Count以字典的当前大小创建一个数组,然后尝试复制项,调用其ConcurrentDictionary<TKey, TValue>实现,如果在此期间将元素添加到字典中,则最后抛出ArgumentException.

I've run into an interesting issue. Knowing that the ConcurrentDictionary<TKey, TValue> is safely enumerable while being modified, with the (in my case) unwanted side-effect of iterating over elements that may disappear or appear multiple times, I decided to create a snapshot myself, using ToList(). Since ConcurrentDictionary<TKey, TValue> also implements ICollection<KeyValuePair<TKey, TValue>>, this causes the List(IEnumerable<T> collection) to be used, which in turn creates an array in the current size of the dictionary using the current item Count, then attempts to copy over the items using ICollection<T>.CopyTo(T[] array, int arrayIndex), calling into its ConcurrentDictionary<TKey, TValue> implementation, and finally throwing an ArgumentException if elements are added to the dictionary in the meantime.

完全锁定将杀死按原样使用集合的意义,因此我的选择似乎是要么继续捕获异常并重试(这绝对不是解决问题的正确答案),要么实施我自己的ToList()的版本专门用于此问题(但再次,仅增加列表然后可能将其修剪为几个元素的合适大小似乎是过大的选择,并且使用LinkedList会降低索引性能).

Locking all over would kill the point of using the collection as it is, so my options seem to be to either keep catching the exception and retrying (which is definitely not the right answer to the problem), or to implement my own version of ToList() specialized for this issue (but then again, simply growing a list then possibly trimming it to the right size for a few elements seems like an overkill, and using a LinkedList would decrease indexing performance).

此外,似乎添加某些在后台创建某种缓冲区的LINQ方法(例如OrderBy)似乎确实以提高性能为代价解决了该问题,但是裸露的ToList()显然可以解决问题.不需要,并且当不需要其他功能时,不值得用另一种方法增强"它.

In addition, it seems like adding certain LINQ methods that create some sort of a buffer in the background (such as OrderBy) do seem to mend the problem at the cost of performance, but the bare ToList() obviously does not, and it's not worth "augmenting" it with another method when no additional functionality is needed.

是否存在任何并发集合的问题?

Could this be an issue with any concurrent collection?

在创建这样的快照时将性能降低到最低程度的合理解决方法是什么? (最好是在一些LINQ魔术的结尾.)

What would be a reasonable workaround to keep performance hits to the minimum while creating such a snapshot? (Preferably at the end of some LINQ magic.)

经过仔细研究后,我可以确认,ToArray()(以为我昨天刚过了它)确实可以解决快照问题,只要它只是一个简单的快照,当附加功能被使用时就无济于事.拍摄快照之前需要(例如过滤,排序),最后仍需要一个列表/数组. (在这种情况下,需要额外的调用,以重新创建新的集合.)

After looking into it I can confirm, ToArray() (to think that I just passed by it yesterday) really does solve the snapshot problem as long as it's just that, a simple snapshot, it does not help when additional functionality is required before taking said snapshot (such as filtering, sorting), and a list/array is still needed at the end. (In this case, an additional call is required, creating the new collection all over again.)

我未能指出快照可能需要也可能不需要进行这些修改,因此最好在最后进行拍摄,因此我将其添加到问题中.

I failed to point out that the snapshot may or may not need to go through these modifications, so it should be taken at the end, preferably, so I'd add this to the questions.

(此外,如果有人对标题有更好的主意,请告诉.)

(Also, if anyone has a better idea for a title, do tell.)

推荐答案

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

Let's answer the broad over-shadowing question here for all the concurrent types:

如果您将处理内部的操作分成多个步骤,其中所有步骤必须同步",则,最终,由于线程的原因,您将崩溃并出现奇怪的结果同步.

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来获取值并将其放置在列表中,然后,肯定是这两个部分将有机会获得不同数量的元素.

So if using .ToList() will first ask for .Count, then size an array, and then use foreach to grab the values and place in the list, then yes, definitively you will have the chance of the two parts getting a different number of elements.

说实话,我希望这些并发类型中的一些不要通过实现许多这些接口来假装它们是正常的集合,但是,事实就是如此.

To be honest I wish some of those concurrent types did not try to pretend they were normal collections by implementing a lot of those interfaces but alas, that's how it is.

现在您知道此问题了,您可以修复您的代码吗?

Can you fix your code, now that you know about the issue?

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

Yes you can, you must take a look at the type documentation and see if it provides any form of snapshotting mechanism that isn't prone to the above mentioned problems.

结果ConcurrentDictionary<TKey, TValue>实现了.ToArray(),它是

Turns out ConcurrentDictionary<TKey, TValue> implements .ToArray(), which is documented with:

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

(我的重点)

.ToArray()当前如何实施?

使用锁,请参见第697行.

Using locks, see line 697.

因此,如果您觉得锁定整个字典来获取快照过于昂贵,那么我会质疑首先获取其内容快照的行为.

So if you feel locking the entire dictionary to get a snapshot is too costly I would question the act of grabbing a snapshot of its contents to begin with.

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

Additionally, the .GetEnumerator() method follows some of the same rules, from the documentation:

从字典中返回的枚举器可以安全地与字典进行读写操作,但是它不代表字典的即时快照.通过枚举器公开的内容可能包含在调用GetEnumerator之后对字典进行的修改.

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()不会崩溃,但它可能不会产生您想要的结果.

So while .GetEnumerator() won't crash, it may not produce the results you want.

取决于时间,.ToArray()都不可能,所以这都取决于.

Depending on timing, neither may .ToArray(), so it all depends.

这篇关于在ConcurrentDictionary&lt; TKey,TValue&gt;上调用ToList().在添加项目时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆