【发布时间】:2008-09-15 20:29:54
【问题描述】:
我需要枚举对象的通用 IList。列表的内容可能会发生变化,例如被其他线程添加或删除,这将终止我的枚举,并显示“集合已修改;枚举操作可能无法执行。”
在 IList 上执行线程安全 foreach 的好方法是什么?最好不要克隆整个列表。无法克隆列表引用的实际对象。
【问题讨论】:
标签: c# multithreading
我需要枚举对象的通用 IList。列表的内容可能会发生变化,例如被其他线程添加或删除,这将终止我的枚举,并显示“集合已修改;枚举操作可能无法执行。”
在 IList 上执行线程安全 foreach 的好方法是什么?最好不要克隆整个列表。无法克隆列表引用的实际对象。
【问题讨论】:
标签: c# multithreading
克隆列表是最简单和最好的方法,因为它可以确保您的列表不会因您而改变。如果列表太大而无法克隆,请考虑在其周围加一个锁,在读取/写入之前必须对其进行锁定。
【讨论】:
没有这样的操作。你能做的最好的就是
lock(collection){
foreach (object o in collection){
...
}
}
【讨论】:
您的问题是枚举不允许 IList 更改。这意味着您在浏览列表时必须避免这种情况。
我想到了几种可能性:
或者,您可以编写自己的 IList 和 IEnumerator 实现,以实现所需的并行访问。不过,恐怕这并不简单。
【讨论】:
ICollection MyCollection;
// Instantiate and populate the collection
lock(MyCollection.SyncRoot) {
// Some operation on the collection, which is now thread safe.
}
来自MSDN
【讨论】:
你会发现这是一个非常有趣的话题。
最好的方法依赖于 ReadWriteResourceLock,由于所谓的 Convoy 问题,它曾经存在很大的性能问题。
我发现处理该主题的最佳文章是 Jeffrey Richter 的 this one,它公开了自己的高性能解决方案方法。
【讨论】:
所以要求是:在添加和删除元素的同时,您需要在不复制的情况下通过 IList 进行枚举。
你能澄清一些事情吗?插入和删除是否只发生在列表的开头或结尾? 如果可以在列表中的任何点进行修改,那么当在枚举当前元素的附近或位置移除或添加元素时,枚举应该如何表现?
这当然可以通过创建一个可能具有整数索引的自定义 IEnumerable 对象来实现,但前提是您可以控制对 IList 对象的所有访问(用于锁定和维护枚举的状态)。但在最好的情况下,多线程编程是一项棘手的工作,而且这是一个复杂的问题。
【讨论】:
Forech 依赖于集合不会改变的事实。如果您想遍历一个可以更改的集合,请使用正常的 for 构造并准备好非确定性行为。锁定可能是一个更好的主意,具体取决于您在做什么。
【讨论】:
简单的索引数据结构(如链表、b 树或哈希表)的默认行为是按从第一个到最后一个的顺序进行枚举。在迭代器已经超过该点之后在数据结构中插入一个元素或插入一个迭代器一旦到达就会枚举的元素不会导致问题,并且应用程序可以检测到这样的事件并在以下情况下进行处理应用程序需要它。为了检测集合中的变化并在枚举过程中抛出错误,我只能想象某人的(坏)想法,即做他们认为程序员想要的事情。事实上,微软已经修复了他们的集合以正常工作。他们在 .NET 4.0 中调用了他们闪亮的新的完整集合 ConcurrentCollections (System.Collections.Concurrent)。
【讨论】:
将列表包装在一个锁定对象中以进行读写。如果你有一个合适的锁,你甚至可以一次迭代多个读取器,它允许多个并发读取器但也允许单个写入器(当没有读取器时)。
【讨论】:
我最近花了一些时间对大型应用程序进行多线程处理,并且在处理跨线程共享的对象列表时遇到了很多问题。
在许多情况下,您可以使用旧的 for 循环并立即将对象分配给一个副本以在循环内使用。请记住,写入列表对象的所有线程都应该写入对象的不同数据。否则,请按照其他贡献者的建议使用锁或副本。
例子:
foreach(var p in Points)
{
// work with p...
}
可以替换为:
for(int i = 0; i < Points.Count; i ++)
{
Point p = Points[i];
// work with p...
}
【讨论】:
这是我最近不得不处理的事情,对我而言,这实际上取决于您对列表的处理方式。
如果您需要在某个时间点使用列表(考虑到当前其中的元素数量)并且另一个线程只能添加到列表的末尾,那么也许您只需切换到 FOR 循环柜台。在您抓住计数器时,您只会看到列表中的 X 个元素。您可以浏览列表(而其他人正在添加到它的末尾)。 . .不应该引起问题。
现在,如果列表需要由其他线程从列表中取出或由其他线程清除,那么您需要实现上述锁定机制之一。此外,您可能想查看一些较新的“并发”集合类(尽管我不相信它们实现 IList - 所以您可能需要重构字典)。
【讨论】: