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 ForEach Loop

The third part of the Parallel Programming in .NET tutorial examines the parallel ForEach loop. This iterates through the items in a collection, executing the same code for each with the potential for the data to be decomposed and processed in parallel.

Parallel Loops

The Task Parallel Library (TPL) includes two loop commands that mimic the for and foreach loops, except that the iterations can be decomposed and executed in parallel on machines with multiple cores. In the previous article we examined the parallel for loop. In this article we will look at the parallel ForEach loop. If you haven't already read the previous article you should consider doing so as the pitfalls for the two loops are the same.

Parallel.ForEach

The parallel ForEach loop provides a parallel version of the standard, sequential foreach loop. Each iteration processes a single item from a collection. However, the parallel nature of the loop means that multiple iterations may be executing at the same time on different processors or processor cores. This opens up the possibility of synchronisation problems so the loop is ideally suited to processes where each iteration is independent of the others.

To demonstrate the use of the parallel ForEach loop and its syntax, create a new console application. Add the following using directive to the automatically generated class, as the loop method is found in the System.Threading.Tasks namespace.

using System.Threading.Tasks;

To begin we'll create a sequential foreach loop that performs a long running task once for each item in a collection. We'll create similar functionality to that of the previous article for this purpose. The code below loops through a list of ten integers, generated using Enumerable.Range. A calculation is performed during each iteration and the number from the collection is outputted alongside the result of that calculation. If the program runs too quickly or too slowly, adjust the upper range of the loop in the GetTotal method to achieve an acceptable speed.

static void Main(string[] args)
{
    foreach (int i in Enumerable.Range(1, 10))
    {
        Console.WriteLine("{0} - {1}", i, GetTotal());
    }
}

static long GetTotal()
{
    long total = 0;
    for (int i = 1; i < 1000000000; i++)    // Adjust this loop according
    {                                       // to your computer's speed
        total += i;
    }
    return total;
}


/* OUTPUT

1 - 499999999500000000
2 - 499999999500000000
3 - 499999995000000000
4 - 499999995000000000
5 - 499999995000000000
6 - 499999995000000000
7 - 499999995000000000
8 - 499999995000000000
9 - 499999995000000000
10 - 499999999500000000

*/

The parallel version of the loop uses the static ForEach method of the Parallel class. There are many overloaded versions of this method but in this article we will use only the simplest one. This accepts two arguments. The first is the collection of objects that will be enumerated. This can be any collection that implements IEnumerable<T>.

The second parameter accepts an Action delegate, usually expressed as a lambda expression, that determines the action to take for each item in the collection. The delegate's parameter contains the item from the collection that is to be processed during an iteration.

The code below shows the parallel version of the earlier sequential loop. Note that the body of the loop is now the body of a statement lambda and the collection is passed to the first parameter of the method.

Parallel.ForEach(Enumerable.Range(1, 10), i => 
{
    Console.WriteLine("{0} - {1}", i, GetTotal());
});

/* OUTPUT

2 - 499999999500000000
1 - 499999999500000000
3 - 499999999500000000
5 - 499999999500000000
4 - 499999999500000000
6 - 499999999500000000
9 - 499999999500000000
7 - 499999999500000000
8 - 499999999500000000
10 - 499999999500000000

*/

As you can see, the results of the individual iterations are identical to the sequential version of the loop. However, the iterations will usually be processed non-sequentially. The output from the example shows the order of one execution of the loop on a dual-core processor. The out-of-sequence result is due to the parallel nature of the operation. The order will vary according to the number of available processor cores and their current utilisation by other software.

18 August 2011