z3nth10n
z3nth10n

Reputation: 2461

Parallel.ForEach is exited before it completes

Well, I'm using Parallel ForEach to process a very big array of Colors (35M of indexers).

I'm using Partitioner.Create to do this with perfomance. But something unexpected is happening:

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;
}

As I wroted on the comment, why is this happening?

The expected behaviour of this, is to process a large image using parallelism, not only a small piece.

processed contains the full image.

dupe contains a empty array that will be completed on each iteration.

I do all this in the local scope to avoid heap problems.

Upvotes: 0

Views: 473

Answers (2)

Jodrell
Jodrell

Reputation: 35746

Don't you want something like, 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;   
    }                
}

Upvotes: 2

Alexandru Clonțea
Alexandru Clonțea

Reputation: 1886

++i; is in fact shorthand for something like this:

  temp = i + 1;
  i = temp;

Luckily you are using int not long, so at least the i = temp; assignment is atomic, the explanation is easier :)

If two threads are both doing ++i; something like this can happen (only two threads considered for simplicity):

//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;

So here you would probably expect i to be 2, but in fact it is 1 by the end of this.

If you want to increment i in a threadsafe manner you can do:

Interlocked.Increment(ref i);

As pointed in your code for currentIndex which should be also calculated like this.

I see another issue with the code given the huge discrepancy in numbers. Exceptions that happen outside of the main thread are not reported to/on the main thread if the IsBackGround property of the thread is true. To guard against this, you should try/catch the inner block in the foreach to also count exceptions in a similar way.

Or even better, get a list of exceptions in a 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);

(source: https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-handle-exceptions-in-parallel-loops)

Upvotes: 2

Related Questions