我正在使用foreach循环遍历元素列表,如下所示:
foreach (Type name in aList) {
name.doSomething();
}
然而,在另一个线程中我称之为
aList.Remove(Element);
在运行时,这会导致InvalidOperationException:Collection已被修改;枚举操作可能无法执行。处理这个问题的最佳方法是什么(即使以性能为代价,我也会认为它相当简单)?
谢谢!
答案 0 :(得分:10)
处理这个问题的最佳方法是什么(即使以性能为代价,我也会认为它相当简单)?
从根本上说:不要尝试从多个线程修改非线程安全的集合而不锁定。您正在迭代的事实在这里几乎无关紧要 - 它只是帮助您更快地找到它。两个线程同时调用Remove
是不安全的。
使用线程安全的集合,例如ConcurrentBag
或,确保只有一个线程与集合任何一次。
答案 1 :(得分:9)
方法#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<T> copy;
lock (aList)
{
copy = new List<T>(aList);
}
foreach (T name in copy)
{
name.doSomething();
}
方法#3:
这一切都取决于你的具体情况,但我通常处理这个问题的方法是保持对集合的主引用不可变。这样你就不必在阅读器端同步访问。作者方面需要lock
。读者方面不需要任何东西,这意味着读者保持高度并发。您唯一需要做的就是将aList
引用标记为volatile
。
// Variable declaration
object lockref = new object();
volatile List<T> aList = new List<T>();
// Writer
lock (lockref)
{
var copy = new List<T>(aList);
copy.Remove(item);
aList = copy;
}
// Reader
List<T> local = aList;
foreach (T name in local)
{
name.doSomething();
}
答案 2 :(得分:8)
主题A:
lock (aList) {
foreach (Type name in aList) {
name.doSomething();
}
}
主题B:
lock (aList) {
aList.Remove(Element);
}
这对于表现来说真的很糟糕。
答案 3 :(得分:1)
如果您有多个阅读器,请尝试使用Reader-Writer Lock(.Net 3.5+),Slim: http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx
如果您只有一个阅读器,只需锁定列表本身或私有对象(但不要锁定类型本身),如Eugen Rieck的回答所示。
答案 4 :(得分:0)
如果您只是想避免使用例外
foreach (Type name in aList.ToArray())
{ name.doSomething(); }
请注意 做一点事() 在另一个线程
中删除元素的情况下也执行答案 5 :(得分:0)
我无法从您的问题中明确说明,但(看起来像)您正在对每个项目执行操作然后将其删除。您可能希望查看BlockingCollection<T>
,其中有一个方法调用GetConsumingEnumerable()
,以确定它是否适合您。这是一个小样本。
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);
}
}