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

Hen*_*son 9 c# multithreading list thread-safety

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

foreach (Type name in aList) {
   name.doSomething();
}
Run Code Online (Sandbox Code Playgroud)

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

aList.Remove(Element);
Run Code Online (Sandbox Code Playgroud)

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

谢谢!

Jon*_*eet 12

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

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

要么使用线程安全的集合,要么确保一次只有一个线程对集合做任何事情.ConcurrentBag


Eug*_*eck 9

线程A:

lock (aList) {
  foreach (Type name in aList) {
     name.doSomething();
  }
}
Run Code Online (Sandbox Code Playgroud)

线程B:

lock (aList) {
  aList.Remove(Element);
}
Run Code Online (Sandbox Code Playgroud)

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


Bri*_*eon 9

方法#1:

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

// Writer
lock (aList)
{
  aList.Remove(item);
}

// Reader
lock (aList)
{
  foreach (T name in aList)
  {
    name.doSomething();
  }
}
Run Code Online (Sandbox Code Playgroud)

方法#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();
}
Run Code Online (Sandbox Code Playgroud)

方法#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();
}
Run Code Online (Sandbox Code Playgroud)