如何正确执行Parallel.ForEach,锁定和进度报告

Joh*_*eer 8 c# locking task-parallel-library

我正在尝试实现Parallel.ForEach模式并跟踪进度,但我遗漏了一些有关锁定的内容.以下示例计数为1000时threadCount = 1,但不是threadCount> 1. 当正确的方法是什么时候?

class Program
{
   static void Main()
   {
      var progress = new Progress();
      var ids = Enumerable.Range(1, 10000);
      var threadCount = 2;

      Parallel.ForEach(ids, new ParallelOptions { MaxDegreeOfParallelism = threadCount }, id => { progress.CurrentCount++; });

      Console.WriteLine("Threads: {0}, Count: {1}", threadCount, progress.CurrentCount);
      Console.ReadKey();
   }
}

internal class Progress
{
   private Object _lock = new Object();
   private int _currentCount;
   public int CurrentCount
   {
      get
      {
         lock (_lock)
         {
            return _currentCount;
         }
      }
      set
      {
         lock (_lock)
         {
            _currentCount = value;
         }
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

svi*_*ick 19

count++从多个线程(共享count变量)调用类似事件的常见问题是这一系列事件可能发生:

  1. 线程A读取的值count.
  2. 线程B读取的值count.
  3. 线程A递增其本地副本.
  4. 线程B递增其本地副本.
  5. 线程A将递增的值写回count.
  6. 线程B将递增的值写回count.

这样,线程A写入的值被线程B覆盖,因此该值实际上只增加一次.

您的代码会在操作1,2(get)和5,6 ()之间添加锁定set,但这对于阻止有问题的事件序列没有任何作用.

你需要做的是锁定整个操作,这样当线程A递增值时,线程B根本无法访问它:

lock (progressLock)
{
    progress.CurrentCount++;
}
Run Code Online (Sandbox Code Playgroud)

如果您知道只需要递增,则可以创建一个Progress封装它的方法.


Roy*_* T. 19

老问题,但我认为有更好的答案.

您可以使用Interlocked.Increment(ref progress)这种方式报告进度,而不必担心将写入操作锁定为进度.