11

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

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

4

11 回答 11

12

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

于 2008-09-15T20:33:15.137 回答
3

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


lock(collection){
    foreach (object o in collection){
       ...
    }
}
于 2008-09-15T20:32:46.540 回答
3

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

想到了几种可能性:

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

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

于 2008-09-15T20:35:32.760 回答
2
ICollection MyCollection;
// Instantiate and populate the collection
lock(MyCollection.SyncRoot) {
  // Some operation on the collection, which is now thread safe.
}

来自MSDN

于 2008-09-15T20:36:22.927 回答
2

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

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

我发现处理该主题的最佳文章是Jeffrey Richter 的这篇文章,它公开了自己的高性能解决方案方法。

于 2008-09-15T20:45:10.537 回答
2

所以要求是:您需要在不复制的情况下枚举 IList<> 同时添加和删除元素。

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

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

于 2008-09-16T01:47:53.273 回答
1

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

于 2008-09-15T20:31:41.987 回答
1

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

于 2009-07-06T20:54:20.457 回答
0

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

于 2008-09-15T20:33:51.420 回答
0

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

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

例子:

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

可以替换为:

for(int i = 0; i < Points.Count; i ++)
{
   Point p = Points[i];
   // work with p...
}
于 2019-02-22T13:41:53.410 回答
0

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

If you need to use the list at a point in time (given the number of elements currently in it) AND another thread can only ADD to the end of the list, then maybe you just switch out to a FOR loop with a counter. At the point you grab the counter, you're only seeing X numbers of elements in the list. You can walk through the list (while others are adding to the end of it) . . . should not cause a problem.

Now, if the list needs to have items taken OUT of it by other threads, or CLEARED by other threads, then you'll need to implement one of the locking mechanisms mentioned above. Also, you may want to look at some of the newer "concurrent" collection classes (though I don't believe they implement IList - so you may need refactor for a dictionary).

于 2020-06-16T18:40:00.730 回答