Parallel.ForEach在完成之前退出

时间:2018-10-11 07:52:17

标签: c# parallel-processing

好吧,我正在使用Parallel ForEach处理大量的颜色(3500万个索引器)。

我正在使用Partitioner.Create来实现这一目标。但是发生了意外情况:

private Color32[] MainGeneration(Color32[] proccesed, Color32[] dupe, int width, int height)
{
    int i = 0;
    Parallel.ForEach(Partitioner.Create(0, totalIndexes), item =>
    {
        int x = i % width;
        int y = height - i / width - 1;

        dupe[i] = (UnityEngine.Color)MapIteration((Color)((Color)(UnityEngine.Color)proccesed[i]).Clone(), i, x, y)
        ++i;
        if (i % updateInterlockedEvery == 0)
        {
            ++currentIndex; //Interlocked.Increment(ref currentIndex);
        }
    });

    // If Parallel.ForEach is blocking, why this is outputting me 12 when total indexes value is 35.000.000?
    Debug.Log(i);
    return dupe;
}

正如我在评论中写道,为什么会这样?

这种预期的行为是使用并行处理大型图像,而不仅仅是一小部分。

processed包含完整图像。

dupe包含一个空数组,它将在每次迭代时完成。

我在本地范围内进行所有操作以避免堆问题。

2 个答案:

答案 0 :(得分:3)

您不需要Fiddle Here

using System.Collections.Concurrent;
using System.Threading.Tasks;

using UnityEngine;

public class Program
{       
    private void MainGeneration(
            Color32[] source,
            Color32[] target,
            int width,
            int height)
    {
        Parallel.ForEach(Partitioner.Create(source, true)
                .GetOrderableDynamicPartitions(), colorItem =>
        {
            var i = colorItem.Key;
            var color = colorItem.Value;
            var x = i % width;
            var y = height - i / width - 1;
            target[i] = this.Map(color, i, x, y);
        });
    }

    private Color32 Map(Color32 color, long i, long x, long y)
    {
        return color;   
    }                
}

答案 1 :(得分:2)

++i;实际上是类似这样的缩写:

  temp = i + 1;
  i = temp;

幸运的是,您使用int的时间不长,因此至少i = temp;分配是原子的,解释起来很容易:)

如果两个线程都在执行++ i;这样的事情可能会发生(为简单起见,只考虑了两个线程):

//Let's say i == 0
Thread 2 calculates i + 1 //== 1
Thread 1 calculates i + 1 //== 1
Thread 1 sets i = 1;
Thread 2 sets i = 1;

所以在这里您可能希望我为2,但实际上到此为止,我为1。

如果要以线程安全的方式递增i,可以执行以下操作:

Interlocked.Increment(ref i);

正如您的currentIndex代码中所指出的那样,也应该像这样计算。

鉴于数字上的巨大差异,我看到了代码的另一个问题。如果线程的IsBackGround属性为true,则不在主线程上/不在主线程上报告在主线程之外发生的异常。为了防止这种情况,您应该尝试/捕获foreach中的内部块,以同样的方式计数异常。

甚至更好的是,在ConcurrentQueue / ConcurrentBag中获取异常列表:

// Use ConcurrentQueue to enable safe enqueueing from multiple threads.
var exceptions = new ConcurrentQueue<Exception>();

// Execute the complete loop and capture all exceptions.
Parallel.ForEach(data, d =>
{
    try
    {
        // Cause a few exceptions, but not too many.
        if (d < 3)
            throw new ArgumentException($"Value is {d}. Value must be greater than or equal to 3.");
        else
            Console.Write(d + " ");
    }
    // Store the exception and continue with the loop.                    
    catch (Exception e)
    {
        exceptions.Enqueue(e);
    }
});
Console.WriteLine();

// Throw the exceptions here after the loop completes.
if (exceptions.Count > 0) throw new AggregateException(exceptions);

(来源:https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-handle-exceptions-in-parallel-loops