Modifying list from another thread while iterating (C#)
我正在使用foreach循环遍历元素列表,如下所示:
1 2 3 | foreach (Type name in aList) { name.doSomething(); } |
然而,在另一条线索中,我称之为
1 | aList.Remove(Element); |
在运行时期间,这会导致InvalidOperationException:集合已被修改;枚举操作可能无法执行。处理这一问题的最佳方法是什么(即使以性能为代价,我也希望它相当简单)?
谢谢!
What is the best way to handle this (I would perfer it to be rather simple even at the cost of performance)?
基本上:不要试图在不锁定的情况下修改来自多个线程的非线程安全集合。你正在迭代的事实在这里基本上是不相关的——它只是帮助你更快地找到它。两个线程同时调用
要么使用线程安全集合,如
方法1:
最简单和最低效的方法是为读者和作者创建一个关键部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Writer lock (aList) { aList.Remove(item); } // Reader lock (aList) { foreach (T name in aList) { name.doSomething(); } } |
方法2:
这与方法1类似,但在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Writer lock (aList) { aList.Remove(item); } // Reader List<T> copy; lock (aList) { copy = new List<T>(aList); } foreach (T name in copy) { name.doSomething(); } |
方法3:
这完全取决于您的具体情况,但我通常处理这一问题的方式是保持集合的主引用不变。这样就不必在读卡器端同步访问。事情的作者需要一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
线程A:
1 2 3 4 5 | lock (aList) { foreach (Type name in aList) { name.doSomething(); } } |
线程B:
1 2 3 | lock (aList) { aList.Remove(Element); } |
当然,这对性能真的很不利。
如果有多个读卡器,请尝试读写器锁(.net 3.5+),Slim:http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx
如果您只有一个读卡器,那么列表本身或私有对象(但不要锁定类型本身),如EugenRieck的答案所示。
我不能从你的问题中明确地分辨出来,但是(看起来)你正在对每个项目执行一个操作,然后删除它。您可能需要查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void SomeMethod() { BlockingCollection<int> col = new BlockingCollection<int>(); 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); } } |
如果您只是想避免异常使用
1 2 | foreach (Type name in aList.ToArray()) { name.doSomething(); } |
意识到剂量学()在另一个线程中删除元素的情况下也执行