OutOfRangeException with Parallel.For

104 Views Asked by At

Ok, so I have a program that works ok. Inside of it there is a for loop that can be parallelized. So I used Parallel.For to do so. It worked ok one or two times, but the other times there's following exception:

Unspecified error One or more errors occurred

No further information, just this helpful message. Anyone has any idea what might be happening?

Edit: Ok, So I nailed it down to an out of range exception. Turns out I was accessing array elements before initializing them, which seems like a race condition. I had this:

 Parallel.For(0, 4, (i, state) =>
        {
            levelTwoPermutationObjects.Add(new permutationObject());
            levelTwoPermutationObjects[i].element = number;
            levelTwoPermutationObjects[i].DoThings();
         });

Which was making the second and third lines access an element that apparently didn't yet exist. I moved the element initialier out of the parallel loop (so that the array is initialized before being accessed), and now it works.

The iterations were almost independent of each other, except on that Add() part, that obviously depended if there was another element before it or not.

1

There are 1 best solutions below

12
On BEST ANSWER

I am risking a shot in the dark: levelTwoPermutationObjects is not thread-safe (ie being a List<T>). You should instead use collections of the namespace System.Collections.Generic.Concurrent, like ConcurrentBag<T> (as there's no thread-safe version of List<T>), as you are suffering a Race condition (please see the example here) with the .Add-call (read without write operations are ok within multithreading):

public void Add(T item) {
    if (_size == _items.Length) EnsureCapacity(_size + 1);
    _items[_size++] = item;
    _version++;
}

Also see the remarks at the MSDN:

It is safe to perform multiple read operations on a List, but issues can occur if the collection is modified while it’s being read. To ensure thread safety, lock the collection during a read or write operation. To enable a collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization. For collections with built-in synchronization, see the classes in the System.Collections.Concurrent namespace. For an inherently thread–safe alternative, see the ImmutableList class.

If you are not willing or unable to adapt the type of levelTwoPermutationObjects you can also utilize a lock-statement like (DANGER DO NOT USE - just for demo):

var @lock = new object();
Parallel.For(0, 4, (i, state) =>
{
    lock (@lock)
    {
        levelTwoPermutationObjects.Add(new permutationObject());
        levelTwoPermutationObjects[i].element = number;
        levelTwoPermutationObjects[i].DoThings();
    }
 });

But this will make the Parallel.For-call useless. You should in fact adapt your code like (if I interpreted your code correctly):

var @lock = new object();
Parallel.For(0, 4, (i, state) =>
{
    var permutationObject = new permutationObject
    {
        element = number
    };
    permutationObject.DoThings();
    lock (@lock)
    {
        levelTwoPermutationObjects.Add(permutationObject);
    }
 });

If .DoThings() of permutationObject is a long running operation, you should fire off and forget the call with eg Task.Run instead of waiting for the result to proceed with the .Add-call.

Otherwise you can transform your processing chain into a seeding-process that adds elements to the collection (which should be a short running operation) and a processing-process (where each iteration can be a long running operation) to avoid a Race condition by only doing reads after sequential writes, like:

var levelTwoPermutationObjects = Enumerable.Range(0, 4)
                                           .Select(arg => new permutationObject
                                                          {
                                                              element = number
                                                          })
                                           .ToList();
Parallel.ForEach(levelTwoPermutationObjects,
                 permutationObject => permutationObject.DoThings());