我有一个ConcurrentBag集合,对于这个中的每个项目,我都会做一些工作。
我知道带ConcurrentBag的Parallel.ForEach是线程安全的,但我从未以这种方式完成并行操作。
我的代码看起来像这样
public void Work(IList<MyModel> data)
{
var tasks = new ConcurrentBag<Task>();
var safeData = new ConcurrentBag<MyModel>(data);
foreach (var item in safeData )
{
var task = Task.Factory.StartNew(() => SomeTask(item));
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
}
那么,这段代码是线程安全的吗?
由于
修改 也许这个问题可能是:“item”是线程安全的还是可以在迭代之间改变它的值?
编辑2: 如果此代码可能属于此problem,我会引用。
编辑3: 在这个question中,“Wonko the sane”在他直接使用循环变量时遇到了问题。我认为使用ConcurrentBag可以解决这个问题,但在某些答案中,告诉我根本不需要ConcurrentBag。所以:
直接使用循环变量是个好主意吗?
何时推荐将值复制到另一个变量?
直接使用ConcurrentBag中的循环变量是否安全?
答案 0 :(得分:3)
您完全不需要ConcurrentBag
代码....而且您ConcurrentBag
甚至不需要Parallel.ForEach
。 Parallel.ForEach
的参数不需要是线程安全的。
相反,你可以简单地写一下,
public void Work(IList<MyModel> data)
{
Parallel.ForEach(data, item=>{
DoSomeTask(item);
});
}
...交替地
public void Work(IList<MyModel> data)
{
Task.WaitAll(data.Select(item=>{
DoSomeTask(item);
}));
}
只有在ConcurrentBag
内执行的代码中对其进行修改时才需要DoSomeTask
。
每个item
是每个任务的独立实例,除非您在集合中有重复的项目。即使您在输入列表中有重复的项目,ConcurrentBag
也无法解决任何问题。
答案 1 :(得分:1)
除现有答案外:
问题不在于多线程或并发。这是关于closures。
foreach
,则取决于您的编译器版本。如果代码将使用旧版本编译,则会导致问题,如that very question中所述。如果使用for
循环,则应始终复制循环变量。有关详细信息,请参阅Jon's answer。ConcurrentBag
不会影响结果
如果您的编译器版本低于4.0.30319.1
(在MS编译器的情况下),则可以安全地关闭循环变量。 More info about compiler versions。同样如该问题的评论中所述,您可以使用静态分析工具来强调此类情况。