IEnumerable <t>线程安全吗?</t>

时间:2011-05-12 13:30:46

标签: c# .net asp.net multithreading

我有一个填充List<T>的主线程。此外,我创建了一个将在不同线程上执行的对象链,需要访问List。原始列表在生成后永远不会被写入。我的想法是将列表作为IEnumerable<T>传递给在其他线程上执行的对象,主要是因为不允许那些实现这些对象的人错误地写入列表。换句话说,如果保证不写入原始列表,多个线程在IEnumerable上使用.Whereforeach是否安全?

如果永远不会更改原始集合,我不确定迭代器本身是否是线程安全的。

6 个答案:

答案 0 :(得分:12)

IEnumerable<T>无法修改。那么什么可以是非线程安全的呢? (如果您不修改实际的List<T>)。

对于非线程安全,您需要编写和读取操作。

“Iterator本身”是为每个foreach实例化的。

编辑:我简化了我的回答,但@Eric Lippert添加了有价值的评论。 IEnumerable<T>没有定义修改方法,但这并不意味着访问运算符是线程安全的(GetEnumeratorMoveNext等等。)最简单的例子: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

(微软已经实现了一个可枚举的线程安全)