我有一个填充List<T>
的主线程。此外,我创建了一个将在不同线程上执行的对象链,需要访问List。原始列表在生成后永远不会被写入。我的想法是将列表作为IEnumerable<T>
传递给在其他线程上执行的对象,主要是因为不允许那些实现这些对象的人错误地写入列表。换句话说,如果保证不写入原始列表,多个线程在IEnumerable上使用.Where
或foreach
是否安全?
如果永远不会更改原始集合,我不确定迭代器本身是否是线程安全的。
答案 0 :(得分:12)
IEnumerable<T>
无法修改。那么什么可以是非线程安全的呢? (如果您不修改实际的List<T>
)。
对于非线程安全,您需要编写和读取操作。
“Iterator本身”是为每个foreach
实例化的。
编辑:我简化了我的回答,但@Eric Lippert添加了有价值的评论。 IEnumerable<T>
没有定义修改方法,但这并不意味着访问运算符是线程安全的(GetEnumerator
,MoveNext
等等。)最简单的例子:GetEnumerator
实现为这样:
IEnumerator
更复杂的例子是缓存。
这很有意思,但幸运的是我不知道任何没有线程安全实现IEnumerable
的标准类。
答案 1 :(得分:8)
调用Where或foreach的每个线程都有自己的枚举器 - 它们不共享同一列表的一个枚举器对象。因此,由于List没有被修改,并且由于每个线程都使用自己的枚举器副本,因此不存在线程安全问题。
你可以在一个线程中看到这个 - 只需创建一个包含10个对象的List,并从该List中获取两个枚举器。使用一个枚举器枚举5个项目,并使用另一个枚举5个项目。您将看到两个枚举器仅通过前5个项目枚举,而第二个枚举器未从第一个枚举器停止的位置开始。
答案 2 :(得分:4)
只要您确定永远不会修改List
,那么从多个线程中读取将是安全的。这包括使用它提供的IEnumerator
个实例。
对于大多数收藏品来说都是如此。事实上,BCL中的所有集合在枚举期间应该是稳定的。换句话说,枚举器不会修改数据结构。我可以想到一些模糊的情况,比如一个splay-tree,枚举它可能修改结构。同样,BCL系列都没有这样做。
答案 3 :(得分:3)
如果您确定在创建后不会修改列表,则应保证将其转换为ReadOnlyCollection<T>。当然,如果您保留只读集合使用的原始列表,您可以对其进行修改,但如果您将原始列表丢弃,则可以有效地将其简化为只读。
来自该系列的线程安全部分:
ReadOnlyCollection可以同时支持多个读者,只要不修改集合。
因此,如果您没有再次触摸原始列表并停止引用它,您可以确保多个线程可以无需担心地读取它(只要您不再尝试再次修改它就不会做任何事情)。 / p>
答案 4 :(得分:3)
换句话说,如果保证不会写入原始列表,那么多个线程是否可以安全使用。在IEnumerable上有哪些或者是foreach?
是的,如果列表发生变异,这只是一个问题。
但请注意,IEnumerable<T>
可以转回列表然后进行修改。
但还有另一种选择:将您的列表包装到ReadOnlyCollection<T>
并传递它。如果你现在丢弃原始列表,你基本上创建了一个新的不可变列表。
答案 5 :(得分:0)
如果您使用的是net framework 4.5或更高版本,这可能是一个很好的解决方案 http://msdn.microsoft.com/en-us/library/dd997305(v=vs.110).aspx
(微软已经实现了一个可枚举的线程安全)