在Parallel.For中修改方法局部变量.这个线程安全吗?

Uri*_*Uri 3 c# multithreading thread-safety task-parallel-library parallel.foreach

我有这段代码:

int totalData = result.Data.Count;
int count = 0;
Parallel.ForEach(result.Data, data =>
{
    try
    {
        EventRange importedEntity = ImportEntity(auxResult.EntityName, data);
        count++;
        EntityImported(importedEntity, count, totalData);
    }
    catch (Exception e)
    {
        exceptions.Enqueue(e);
    }
});
Run Code Online (Sandbox Code Playgroud)

EntityImported是一个事件,应该说已经处理了多少实体,以及我应该处理多少实体.我担心的是增加lambda内部计数的线程安全性,以及您建议采用哪些步骤来确保始终使用count变量的正确值触发事件.

Jon*_*eet 9

目前它根本不是线程安全的.

你可以使用Interlocked.Increment(ref count),但通常最好为每个线程都有一个"本地"值,并在最后加上它们.这样,除了分配要处理的项目之外,您不需要任何类型的跨线程数据流.

Parallel.ForEach设计用于此目的的重载- 查看文档底部附近的示例,因为它正在执行与您的代码非常相似的操作.(它维护一个本地计数,然后在最后对计数求和.)不幸的是,由于您需要在每次迭代时引发事件的方式,这对您的特定情况没有帮助 - 但通常这是一种更好的方法.

在这种情况下,您应该Interlocked.Increment在事件提升代码中使用结果:

Parallel.ForEach(result.Data, data =>
{
    try
    {
        EventRange importedEntity = ImportEntity(auxResult.EntityName, data);
        int newCount = Interlocked.Increment(ref count);
        EntityImported(importedEntity, newCount, totalData);
    }
    catch (Exception e)
    {
        exceptions.Enqueue(e);
    }
});
Run Code Online (Sandbox Code Playgroud)

这样,每个计数都会引发一个事件(因此一个为0,一个为1,一个为2等).

  • @Uri:您仍然遇到Interlocked.Increment和EntityImported调用之间的问题,计数可能已经改变.优化器可以针对该值优化返回内存,但可能不会.它可能不会那么快,但您可能想在lambda中创建一个局部变量,在递增计数之前锁定,然后复制本地计数.例如=> importedEntity = blah(); int internalcount; lock(lock_object){interlocked.inc(count); internalcount = count;} EntityImported(count,importedEntity); (2认同)
  • @Bengie:比赛是个好点,但没有必要使用锁定.只需使用`Interlocked.Increment`的返回值 - 请参阅我编辑的答案以获取示例代码. (2认同)
  • @JonSkeet:我刚刚学到了新东西,Interlocked.Increment返回了值.谢谢! (2认同)