BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Parallel and Asynchronous
.NET 4.0+

Parallel For Loops for any Data Type

The standard parallel for loop is limited to working with integer values and only permits looping through an incrementing range, unlike its sequential equivalent. This article describes an alternative parallel for loop without these limitations.

Parallel.For

The parallel version of the for loop, which is part of the Task Parallel Library (TPL) introduced with .NET 4.0, allows you to loop through a sequence of incrementing integer values. The iterations of the loop have the possibility of executing in parallel for improved performance.

Parallel.For is similar to a common use of the standard, sequential for loop. However, the non-parallel version is not limited to integer values. It allows tight control over the generation of each item in the sequence and lets you specify the condition that ends the loop.

An example of a for loop that has no equivalent using Parallel.For is shown below. Here we start the loop by initialising a string with a single asterisk (*). Each subsequent iteration adds an asterisk to the string. The loop exits when the length of the loop control variable reaches ten characters.

for (string s = "*"; s.Length < 10; s += "*")
{
    Console.WriteLine(s);
}

/* OUTPUT
     
*
**
***
****
*****
******
*******
********
*********

*/

It's a shame that this type of functionality is not supported by Parallel.For. In this article I'll describe a new method that allows such a loop whilst executing the iterations in parallel if possible.

Parallel.For via Parallel.ForEach

To create the new method we need a way to loop through a series of values in parallel but where the data type is not restricted to integers. The other TPL parallel loop, Parallel.ForEach, does exactly this. The difference is that the sequence of values for the iterations must already exist.

To leverage Parallel.ForEach, we can simply generate a sequence based upon the start and end values of the desired loop range. To minimise the overhead of creating the sequence, and to allow the loop to begin processing values as quickly as possible, we can create the sequence using an iterator for deferred execution.

Let's start with a simple example. We'll swap integers for decimals in this case. The first method that we need is one that converts start and end values into a sequence that can be enumerated over by a parallel loop. The code below shows this method. You can see that it uses a basic for loop, along with a yield return statement to create the iterator. The increment parameter allows finer control over the individual values.

static IEnumerable<decimal> GenerateSequence(decimal start, decimal end, decimal increment)
{
    for (decimal d = start; d < end; d += increment)
    {
        yield return d;
    }
}

We can use the GenerateSequence return value as the basis for the first version of our new method. To operate in a similar manner to Parallel.For method, we need to accept the start and end of the range, as well as our increment parameter. For the final parameter we need an Action delegate. This will be provided with the code to run during each cycle of the loop.

The code for the method, named ParallelFor, is shown below. It's marked as static so that it can be called from the Main method of a console application without instantiating an object.

static void ParallelFor(
    decimal start, decimal end, decimal increment, Action<decimal> function)
{
    var values = GenerateSequence(start, end, increment);
    Parallel.ForEach(values, function);
}

We can now try out the new method. Add the following call to the Main method of a console application and run the program. You can see from the sample results in the comment that all of the values between the start and end values, excluding the end value itself, are outputted. The items are not in ascending order because the loop was processed in parallel.

ParallelFor(0, 5M, 0.1M, d => { Console.Write("{0:F1} ", d); });

/* OUTPUT
     
0.0 0.5 0.6 0.7 0.8 0.2 0.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4
2.5 2.6 2.7 1.1 0.3 3.3 3.4 3.5 3.6 3.7 3.8 3.9 4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7
4.8 4.9 2.8 2.9 3.0 3.1 0.9 1.0 0.4 3.2

*/
5 October 2013