如何从Parallel.ForEach循环中抛出异常?

时间:2015-06-04 16:18:51

标签: c# foreach exception-handling parallel-processing

我有一个Parallel.Foreach循环,可以下载文件:

try
{
     var parallelOptions = new ParallelOptions();
     parallelOptions.MaxDegreeOfParallelism = 8;
     int failedFiles = 0;
     Parallel.ForEach(FilesToDownload, parallelOptions, tile =>
     {
         bool downloaded = DownloadTile(File);
         if (downloaded)
         {
              //Downloaded :)
         }
         else
         {
               failedFiles++;
               if (failedFiles > 10)
               {
                   throw new Exception("10 Files Failed to download. Failing download");
               }
          } 
          parallelOptions.CancellationToken.ThrowIfCancellationRequested();
      });
}
catch (Exception ex)
{
    throw; //This throws it up to a main method that cancels my program
}

我想知道从Exception方法中抛出Parallel.Foreach的正确方法是什么?在我的实例中,我认为只要抛出第一个异常,我就会看到抛出异常8次。

Parallel.ForEach循环中抛出异常的正确方法是什么?

2 个答案:

答案 0 :(得分:5)

首先,最好使用Interlocked.Increment(ref failedFiles)代替failedFiles++。否则可能会发生10-15次失败,但由于缺少缓存同步和编译器/抖动优化的影响,最终会得到一个值为7-8的计数器。 程序的循环可能会抛出更多异常,但最后它将聚合为单个AggregateException,而outter catch()将接收该单个异常实例。如果您不想在AggregateException中添加更多例外,则可以使用==代替>

if (Interlocked.Increment(ref failedFiles) == 10)

当循环内部抛出异常时,Parallel.ForEach阻止其他迭代启动,然后它等待当前正在运行的迭代完成,然后它聚合它捕获的所有异常,将其打包成{{1并抛出该单个实例。这意味着在您的情况下,单个例外将阻止进一步下载。

答案 1 :(得分:0)

您可以使用CancellationToken.Register。另请查看this example

您也可以停止Parallel.Foreach并抛出异常。

example演示了如何停止For循环;但是,您可以以相同的方式停止ForEach循环。请参阅下面的代码段。

private static void StopLoop()
{
    double[] source = MakeDemoSource(1000, 1);
    ConcurrentStack<double> results = new ConcurrentStack<double>();

    // i is the iteration variable. loopState is a  
    // compiler-generated ParallelLoopState
    Parallel.For(0, source.Length, (i, loopState) =>
    {
        // Take the first 100 values that are retrieved 
        // from anywhere in the source. 
        if (i < 100)
        {
            // Accessing shared object on each iteration 
            // is not efficient. See remarks. 
            double d = Compute(source[i]);
            results.Push(d);
        }
        else
        {
            loopState.Stop();
            return;
        }

    });

    Console.WriteLine("Results contains {0} elements", results.Count());
}

如果需要,您可以使用ConcurrentStack以相同的方式收集异常,以便在循环停止后立即返回。