Share via


Parallel Programming in .NET 4.0: Static Parallel Class (For, ForEach, Invoke)

More great support for parallel computing in .NET 4.0 can be found in the static Parallel class.  This static class provides support for parallelizing regions of your code.  It’s located in the namespace System.Threading.Tasks. 

For example, you probably often write code like this:

 // For loop
for (int i = 0; i < n; i++)
{
    DoWork(i);
}

// Foreach loop
foreach (var item in collection)
{
    DoWork(item);
}

// Invoking multiple statements
StatementA();
StatementB();
StatementC();

With the Parallel class, you can invoke these same constructs in parallel. 

 // Parallel for loop
Parallel.For(0, n, i =>
{
    DoWork(i);
});

// Parallel foreach loop
Parallel.ForEach(collection, item =>
{
    DoWork(item);
});

// Invoking multiple statements in parallel
Parallel.Invoke(
    () => StatementA(),
    () => StatementB(),
    () => StatementC());

The syntax used by the Parallel class methods tries to stay pretty close to the original syntax, with the addition of some lambda expressions to define delegates.  You simply express which code you want to run concurrently, and the runtime manages the thread scheduling details. 

Now, note that the official MSDN documentation says that using the Parallel.* versions may cause this code to execute in parallel.  Parallelism is not guaranteed.  Here are some scenarios when using the Parallel.* methods would not give you parallelism:

1) You have a single-core machine.

2) All of the ThreadPool's threads are already saturated with work so it doesn't make sense to further fan out.

3) You're using a custom task scheduler that forces sequential execution.

Next, keep in mind that it may not always make sense to parallelize (for instance) a for loop.  If later iterations in the loop depend on data from earlier iterations of the loop, executing in parallel won’t work.  There can also be side effects of loops that would be problematic when executed in parallel.  Always test and always measure your performance. 

Finally, let’s look at an example from the "Parallel Programming with the .NET Framework 4" Code Samples.  From reading Stephen Toub’s tour through the samples, you can see that there are many samples that use the Parallel class.  Let’s look at the BlendImages example. 

BlendImages takes two images and blends them together.  You can process either sequentially or in parallel, and compare how long each takes.  Now, of course, the ideal application for this program is to see what the child of Bill Gates and I would look like.* 

Run the program.   To select pictures to blend, double-click on the image box.  Once you have images in the first two boxes, click the “Sequential” button at the top.  Admire the results and note the amount of time it took.  Then click the “Parallel” button.  You will see the time that the parallel results took, in addition to a “Speedup” calculation in the upper right-hand corner.  (SIDE NOTE: perhaps Bill Gates and I shouldn’t have children.) 

BlendImagesResults

The code that executes the blending can be found in MainForm.cs in the Blend Images sample.  Note the use of Parallel.For vs. a regular for loop: 

 if (parallel)
{
    // Blend the images in parallel
    sw.Restart();
    Parallel.For(0, height, j =>
    {
        PixelData* outPixel = fastOut.GetInitialPixelForRow(j);
        PixelData* startPixel = fastStart.GetInitialPixelForRow(j);
        PixelData* endPixel = fastEnd.GetInitialPixelForRow(j);

        for (int i = 0; i < width; i++)
        {
            // Blend the input pixels into the output pixel
            outPixel->R = (byte)((startPixel->R * blend) + .5 + (endPixel->R * (1 - blend))); // .5 for rounding
            outPixel->G = (byte)((startPixel->G * blend) + .5 + (endPixel->G * (1 - blend)));
            outPixel->B = (byte)((startPixel->B * blend) + .5 + (endPixel->B * (1 - blend)));

            outPixel++;
            startPixel++;
            endPixel++;
        }
    });
    sw.Stop();
}
else
{
    // Blend the images sequentially
    sw.Restart();
    for(int j=0; j<height; j++)
    {
        PixelData* outPixel = fastOut.GetInitialPixelForRow(j);
        PixelData* startPixel = fastStart.GetInitialPixelForRow(j);
        PixelData* endPixel = fastEnd.GetInitialPixelForRow(j);

        for (int i = 0; i < width; i++)
        {
            // Blend the input pixels into the output pixel
            outPixel->R = (byte)((startPixel->R * blend) + .5 + (endPixel->R * (1 - blend))); // .5 for rounding
            outPixel->G = (byte)((startPixel->G * blend) + .5 + (endPixel->G * (1 - blend)));
            outPixel->B = (byte)((startPixel->B * blend) + .5 + (endPixel->B * (1 - blend)));

            outPixel++;
            startPixel++;
            endPixel++;
        }
    }
    sw.Stop();
}

Stay tuned for tomorrow’s post, when I will discuss Tasks in the Task Parallel Library. 

* Done for humor only.  I am a happily-married woman.  Sorry, Bill.