从另一个线程修改ConcurrentBag对象

时间:2017-11-17 08:37:33

标签: c# multithreading concurrentmodification

我有一个线程#1,它通过ConcurrentBag列表与foreach一起循环并修改项目,但是在某个时候另一个线程#2需要修改列表中的项目。如果我从线程#2取出所有对象(而另一个用foreach循环)并修改对象然后在线程#2中添加所有项目,这是否安全?如果不是那么有解决方法吗?

ConcurrentBag<MyObject> list;

第1个帖子

while (1) // continuous loop
{
  foreach(MyObject obj in list)
  {
     obj.RunMethod();
     Thread.Sleep(500);
  }
  Thread.Sleep(1000);
}

第2个帖子

(在某些时候,在线程#2上调用回调并需要修改对象)

List<MyObject> temp;
while (list.TryTake(out obj)
{
   MyObject obj2=obj.Clone(); // make a copy of the object
   if (obj2.Id==4) obj2.variable=10;
   temp.Add(obj2);
}
// Add objects back to concurrentbag
foreach(MyObject obj in temp) list.Add(obj);

1 个答案:

答案 0 :(得分:4)

不,这是不安全的,因为执行foreach的线程可能已检索到对象的引用并正在使用该对象,而另一个线程将其从包中移除,修改它并将其放回。

这可能导致对象的状态在list.TakeOut(out obj)线程正在使用时发生变化。您可以通过创建要变异的对象的副本来避免这种情况,然后更改副本并将副本放回到包中。

您是否考虑过使您的对象不可变?这样做会真正简化事情,实际上两个线程不可能同时改变它。

注意:复制对象的引用不会复制对象的内容,因此当您执行obj.Clone()时,您将收到对现有对象的引用,而不是创建它的新副本。

[编辑]因此,您修改了问题以使用ContentType。如果这对您的对象进行了正确的深度克隆,那么代码应该就可以了,但这只是在您需要将对象复制到更改的地方时。如果你使类不可变,那么你保证这种行为,这是你可以使用它的最佳解决方案。