C# 线程安全的 foreach 列表枚举

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/66622/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 11:03:13  来源:igfitidea点击:

Threadsafe foreach enumeration of lists

提问by Radu094

I need to enumerate though generic IList<> of objects. The contents of the list may change, as in being added or removed by other threads, and this will kill my enumeration with a "Collection was modified; enumeration operation may not execute."

我需要枚举对象的通用 IList<>。列表的内容可能会更改,如被其他线程添加或删除,这将终止我的枚举,并显示“集合已修改;枚举操作可能无法执行”。

What is a good way of doing threadsafe foreach on a IList<>? prefferably without cloning the entire list. It is not possible to clone the actual objects referenced by the list.

在 IList<> 上执行线程安全 foreach 的好方法是什么?最好不要克隆整个列表。无法克隆列表引用的实际对象。

采纳答案by John Millikin

Cloning the list is the easiest and best way, because it ensures your list won't change out from under you. If the list is simply too large to clone, consider putting a lock around it that must be taken before reading/writing to it.

克隆列表是最简单和最好的方法,因为它可以确保您的列表不会从您身下改变。如果列表太大而无法克隆,请考虑在它周围放置一个锁,该锁必须在读取/写入它之前进行。

回答by Patrick

Forech depends on the fact that the collection will not change. If you want to iterate over a collection that can change, use the normal for construct and be prepared to nondeterministic behavior. Locking might be a better idea, depending on what you're doing.

Forech 取决于集合不会改变的事实。如果您想迭代一个可以更改的集合,请使用正常的 for 构造并准备好应对不确定的行为。锁定可能是一个更好的主意,这取决于您在做什么。

回答by Jason Punyon

There is no such operation. The best you can do is

没有这样的操作。你能做的最好的是


lock(collection){
    foreach (object o in collection){
       ...
    }
}

回答by jamuraa

Wrap the list in a locking object for reading and writing. You can even iterate with multiple readers at once if you have a suitable lock, that allows multiple concurrent readers but also a single writer (when there are no readers).

将列表包装在一个锁定对象中以供读取和写入。如果你有一个合适的锁,你甚至可以一次迭代多个读取器,这允许多个并发读取器和单个写入器(当没有读取器时)。

回答by jan.vdbergh

Your problem is that an enumeration does not allow the IList to change. This means you have to avoid this while going through the list.

您的问题是枚举不允许 IList 更改。这意味着您在浏览列表时必须避免这种情况。

A few possibilities come to mind:

想到了几种可能性:

  • Clone the list. Now each enumerator has its own copy to work on.
  • Serialize the access to the list. Use a lock to make sure no other thread can modify it while it is being enumerated.
  • 克隆列表。现在每个枚举器都有自己的副本来处理。
  • 序列化对列表的访问。使用锁来确保在枚举它时没有其他线程可以修改它。

Alternatively, you could write your own implementation of IList and IEnumerator that allows the kind of parallel access you need. However, I'm afraid this won't be simple.

或者,您可以编写自己的 IList 和 IEnumerator 实现,以允许您需要的那种并行访问。不过,恐怕这不会简单。

回答by Forgotten Semicolon

ICollection MyCollection;
// Instantiate and populate the collection
lock(MyCollection.SyncRoot) {
  // Some operation on the collection, which is now thread safe.
}

From MSDN

来自MSDN

回答by Jorge Córdoba

You'll find that's a very interesting topic.

你会发现这是一个非常有趣的话题。

The best approach relies on the ReadWriteResourceLock which use to have big performance issues due to the so called Convoy Problem.

最好的方法依赖于 ReadWriteResourceLock,由于所谓的 Convoy Problem,它曾经有很大的性能问题。

The best article I've found treating the subject is this oneby Jeffrey Richter which exposes its own method for a high performance solution.

最好的文章中,我已经找到了治疗的主体是这一个由杰弗里里希特暴露自己的方法对高性能的解决方案。

回答by Jeffrey L Whitledge

So the requirements are: you need to enumerate through an IList<> without making a copy while simultaniously adding and removing elements.

所以要求是:您需要通过 IList<> 枚举而不进行复制,同时添加和删除元素。

Could you clarify a few things? Are insertions and deletions happening only at the beginning or end of the list? If modifications can occur at any point in the list, how should the enumeration behave when elements are removed or added near or on the location of the enumeration's current element?

你能澄清一些事情吗?插入和删除只发生在列表的开头还是结尾?如果在列表中的任何点都可以进行修改,那么在枚举的当前元素的位置附近或附近移除或添加元素时,枚举应该如何表现?

This is certainly doable by creating a custom IEnumerable object with perhaps an integer index, but only if you can control all access to your IList<> object (for locking and maintaining the state of your enumeration). But multithreaded programming is a tricky business under the best of circumstances, and this is a complex probablem.

这当然可以通过创建一个带有整数索引的自定义 IEnumerable 对象来实现,但前提是您可以控制对 IList<> 对象的所有访问(用于锁定和维护枚举的状态)。但在最好的情况下,多线程编程是一项棘手的业务,这是一个复杂的问题。

回答by Jeffrey L Whitledge

Default behavior for a simple indexed data structure like a linked list, b-tree, or hash table is to enumerate in order from the first to the last. It would not cause a problem to insert an element in the data structure after the iterator had already past that point or to insert one that the iterator would enumerate once it had arrived, and such an event could be detected by the application and handled if the application required it. To detect a change in the collection and throw an error during enumeration I could only imagine was someone's (bad) idea of doing what they thought the programmer would want. Indeed, Microsoft has fixed their collections to work correctly. They have called their shiny new unbroken collections ConcurrentCollections (System.Collections.Concurrent) in .NET 4.0.

简单索引数据结构(如链表、b 树或哈希表)的默认行为是按从第一个到最后一个的顺序枚举。在迭代器已经通过该点之后在数据结构中插入一个元素或插入一个迭代器在它到达后将枚举的元素不会导致问题,并且这样的事件可以被应用程序检测到并处理,如果应用程序需要它。为了检测集合中的变化并在枚举期间抛出错误,我只能想象是某人(坏)做他们认为程序员想要的事情的想法。事实上,微软已经修复了他们的集合以正常工作。他们在 .NET 4.0 中将他们闪亮的全新完整集合称为 ConcurrentCollections (System.Collections.Concurrent)。

回答by folmerbrem

I recently spend some time multip-threading a large application and had a lot of issues with the foreach operating on list of objects shared across threads.

我最近花了一些时间对一个大型应用程序进行多线程处理,并且在对跨线程共享的对象列表进行操作时遇到了很多问题。

In many cases you can use the good old for-loop and immediately assign the object to a copy to use inside the loop. Just keep in mind that all threads writing to the objects of your list should write to different data of the objects. Otherwise, use a lock or a copy as the other contributors suggest.

在许多情况下,您可以使用旧的 for 循环并立即将对象分配给一个副本以在循环内使用。请记住,所有写入列表对象的线程都应该写入对象的不同数据。否则,请按照其他贡献者的建议使用锁或副本。

Example:

例子:

foreach(var p in Points)
{
    // work with p...
}

Can be replaced by:

可以替换为:

for(int i = 0; i < Points.Count; i ++)
{
   Point p = Points[i];
   // work with p...
}