在c#中,如何在multithreading环境中迭代IEnumerable

我在这种情况下,有一个大字典,由一个线程以相当高的频率随机更新,还有另一个线程试图将字典的快照保存为历史记录。 我目前正在使用这样的东西:

Dictionary dict = new Dictionary(); var items = dict.Values.ToList(); 

这在大多数情况下都可以正常工作,除非它偶尔抛出:

System.InvalidOperationException:集合已被修改; 枚举操作可能无法执行。

我理解为什么会这样,但我不知道我该怎么做才能避免收集修改错误。

迭代此类集合的最佳方法是什么?

我也试过ConcurrentDictionary,但没有运气。 为什么? ConcurrentDictionary线程只在项目级别安全吗?

根据文档,您应该能够使用ConcurrentDictionary的GetEnumerator()方法来获取线程安全的迭代器。

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

既然你正在处理并发线程,那么在一致性方面进行一些权衡并不奇怪,但我希望这种方法能够阻止其他答案中给出的暴力方法。 如果您尝试过,这将不起作用:

 var items = concurrentDict.Items.ToList(); 

但它应该适用于

 var items = concurrentDict.GetEnumerator(); 

或者您可以直接引用迭代器:

 foreach(var item in concurrentDict) { valueList.Add(item.Value); } 

ImmutableDictionary可能适合您,因为它支持可扩展的multithreading快照作为其基本function集的一部分。

 // initialize. ImmutableDictionary dict = ImmutableDictionary.Create(); // create a new dictionary with "foo" key added. ImmutableDictionary newdict = dict.Add("foo", 0); // replace dict, thread-safe, with a new dictionary with "bar" added. // note this is using dict, not newdict, so there is no "foo" in it. ImmutableInterlocked.TryAdd(ref dict, "bar", 1); // take a snapshot, thread-safe. ImmutableDictionary snapshot = dict; 

不可变性意味着字典永远不会改变 – 您只能通过创建新字典来添加值。 而且由于这个属性,你可以通过简单地从想要快照的点保持一个参考来获取它的“快照”。

它在引擎盖下进行优化以提高效率,而不是为每个操作复制整个事物。 也就是说,对于其他操作来说,它不如ConcurrentDictionary那么高效,但它只是你想要的权衡。 例如,可以并发枚举ConcurrentDictionary但不可能枚举它的快照。

您可以使用带有lock关键字的监视器来确保此时仅执行读取或仅执行写入。

 public class SnapshotDictionary : IEnumerable> { private readonly Dictionary _dictionary = new Dictionary(); private readonly object _lock = new object(); public void Add(TKey key, TValue value) { lock (_lock) { _dictionary.Add(key, value); } } // TODO: Other necessary IDictionary methods public Dictionary GetSnaphot() { lock (_lock) { return new Dictionary(_dictionary); } } public IEnumerator> GetEnumerator() { return GetSnaphot().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } 

GetSnapshot方法返回字典的快照。
我还覆盖了GetEnumerator以便它创建一个快照,然后返回快照的枚举器。

因此,这将起作用,因为将在快照上执行:

 var items = snapshotDictionary.GetSnapshot().Values.ToList(); // or foreach (var item in snapshotDictionary) { // ... } 

但是,这种方法不允许multithreading写入。