在迭代时修改另一个线程的列表(C#)

我正在使用foreach循环遍历元素列表,如下所示:

foreach (Type name in aList) { name.doSomething(); } 

但是,在另一个线程中我称之为

 aList.Remove(Element); 

在运行时,这会导致InvalidOperationException:Collection已被修改; 枚举操作可能无法执行。 处理这个问题的最佳方法是什么(即使以性能为代价,我会更加简单)?

谢谢!

线程A:

 lock (aList) { foreach (Type name in aList) { name.doSomething(); } } 

线程B:

 lock (aList) { aList.Remove(Element); } 

这个过程对于表现来说真的很糟糕。

处理这个问题的最佳方法是什么(即使以性能为代价,我会更加简单)?

从根本上说:不要尝试在没有锁定的情况下从多个线程修改非线程安全的集合。 您正在迭代的事实在这里几乎无关紧要 – 它只是帮助您更快地找到它。 两个线程同时调用Remove是不安全的。

使用诸如ConcurrentBag类的线程安全集合, 或者确保一次只有一个线程对集合执行任何操作。

方法#1:

最简单,效率最低的方法是为读者和作者创建一个关键部分。

 // Writer lock (aList) { aList.Remove(item); } // Reader lock (aList) { foreach (T name in aList) { name.doSomething(); } } 

方法#2:

这类似于方法#1,但是不是在foreach循环的整个持续时间内持有锁,而是先复制集合,然后遍历副本。

 // Writer lock (aList) { aList.Remove(item); } // Reader List copy; lock (aList) { copy = new List(aList); } foreach (T name in copy) { name.doSomething(); } 

方法#3:

这一切都取决于您的具体情况,但我通常处理这个问题的方法是保持对集合的主引用不可变。 这样你就不必在阅读器端同步访问。 作家方面需要lock 。 读者方面不需要任何东西,这意味着读者保持高度并发。 您唯一需要做的是将aList引用标记为volatile

 // Variable declaration object lockref = new object(); volatile List aList = new List(); // Writer lock (lockref) { var copy = new List(aList); copy.Remove(item); aList = copy; } // Reader List local = aList; foreach (T name in local) { name.doSomething(); } 

如果您有多个阅读器,请尝试使用Reader-Writer Lock(.Net 3.5+),Slim: http : //msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx

如果你只有一个阅读器,只需要锁定列表本身或私有对象(但不要锁定类型本身),如Eugen Rieck的回答所示。

如果你只是想避免使用Exception

 foreach (Type name in aList.ToArray()) { name.doSomething(); } 

请注意,在另一个线程中删除元素的情况下也会执行doSomething()

我无法从您的问题中明确说明,但(看起来像)您正在对每个项目执行操作然后将其删除。 你可能想要查看BlockingCollection ,它有一个调用GetConsumingEnumerable()的方法来查看它是否适合你。 这是一个小样本。

 void SomeMethod() { BlockingCollection col = new BlockingCollection(); Task.StartNew( () => { for (int j = 0; j < 50; j++) { col.Add(j); } col.CompleteAdding(); }); foreach (var item in col.GetConsumingEnumerable()) { //item is removed from the collection here, do something Console.WriteLine(item); } }