Threadsafe foreach枚举列表

时间:2008-09-15 20:29:55

标签: c# multithreading

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

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

11 个答案:

答案 0 :(得分:12)

克隆列表是最简单,最好的方法,因为它可以确保您的列表不会从您的下方更改。如果列表太大而无法克隆,请考虑在读取/写入之前对其进行锁定。

答案 1 :(得分:3)

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

我想到了一些可能性:

  • 克隆列表。现在每个枚举器都有自己的副本可供使用。
  • 序列化对列表的访问。使用锁定以确保在枚举时没有其他线程可以修改它。

或者,您可以编写自己的IList和IEnumerator实现,它允许您需要的那种并行访问。但是,我担心这不会很简单。

答案 2 :(得分:3)

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

最好的方法依赖于ReadWriteResourceLock,由于所谓的Convoy问题,它会出现很大的性能问题。

杰弗里里希特(Jeffrey Richter)发表的this one文章是我发现的最好的文章,它揭示了自己的高性能解决方案。

答案 3 :(得分:2)

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


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

答案 4 :(得分:1)

Forech取决于收集不会改变的事实。如果要迭代可以更改的集合,请使用构造的法线并准备好非确定性行为。锁定可能是一个更好的主意,取决于你正在做什么。

答案 5 :(得分:1)

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

来自MSDN

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:1)

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

答案 8 :(得分:0)

将列表包装在锁定对象中以进行读写。如果你有一个合适的锁,你甚至可以一次迭代多个读者,允许多个并发读者,但也允许一个单独的读者(当没有读者时)。

答案 9 :(得分:-1)

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

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

示例:

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

可以替换为:

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

答案 10 :(得分:-1)

这是我最近不得不处理的事情,对我而言,这实际上取决于您对列表的处理方式。

如果您需要在某个时间点使用列表(考虑到当前列表中的元素数量),而另一个线程只能添加到列表的末尾,那么也许您只是切换到带有计数器。在您获得计数器的那一刻,您只看到列表中X个元素。您可以浏览列表(其他添加到列表的末尾)。 。 。应该不会造成问题。

现在,如果列表需要其他线程从中删除项目,或者其他线程将其清除,那么您将需要实现上述锁定机制之一。另外,您可能希望查看一些较新的“并发”集合类(尽管我不相信它们实现了IList-因此您可能需要重构字典)。