RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Getting Started with the .NET Task Parallel Library : Page 3

If you have a multi-core computer, chances are your first CPU does all the work while the others spend most of their time twiddling their electronic thumbs. Learn to unlock the idle power of your underused CPUs to greatly improve the performance of your applications.


Parallel.For and Parallel.ForEach
The InvokeTasks program uses routines that just sleep partly to keep things simple and partly because I had trouble thinking of a good example that needed to execute a small fixed number of tasks in parallel. Usually, I want to perform either a single calculation or break a big problem into dozens of sub-problems and perform them all at once.

For example, suppose I want to generate Mandelbrot sets. It would be nice to break the image into a bunch of pieces and generate them all simultaneously. (I've done this as a distributed application before, farming out pieces to different computers on a network, and it's a relatively large amount of work.)

You could build an array of System.Action objects representing a set of subroutine calls and then call Parallel.Invoke, but that would be awkward at best. One particularly tricky problem would be figuring out which task should generate which part of the image. The call to Parallel.Invoke does not allow you to pass parameters to the tasks so there's no easy way to tell them which part of the image to build. You could make two separate routines to generate the left and right halves of the image but then you'd have to write a lot of duplicated code. That solution would also be hard to generalize to a four or eight-core system.

A better solution would be to use a single routine and pass in a parameter telling it what part of the image to build. While Parallel.Invoke won't let you do that, TPL has other methods that will. The Parallel.For method essentially performs a parallel For loop. It takes as parameters an inclusive lower and an exclusive upper bound for the loop, and the address of a subroutine to execute. It calls the subroutine on different threads, passing it the looping value.

For example, the following Visual Basic code calls the Delay subroutine in parallel. The loop passes values 1 through 3 (it stops before the upper bound of 4) and calls Delay, passing it the looping values 1, 2, and 3:

   Parallel.For(1, 4, AddressOf Delay)

The following code shows the C# version:

   Parallel.For(1, 4, Delay);

In other words, the Parallel.For routine lets you call a subroutine, passing it a series of integer values. That's just the ticket for building a Mandelbrot set in parallel. The call to Parallel.For can loop through the image's X coordinate values, calling a subroutine that generates the part of the image for that vertical stripe through the image. You'll see more about this program in the follow-up case studies article.

You've seen one way to use the Parallel.For method: calling a subroutine while passing it a series of numbers. But suppose you don't want to pass it a series of numbers? Suppose you want to pass it a group of numbers that are not in a simple sequence? Or suppose you want to pass it a series of strings, structures, or other non-numeric objects?

Parallel.ForEach handles those situations. It takes as parameters an IEnumerable collection of values and a subroutine. It then calls the subroutine in parallel, passing it the values in the collection.

As an example, the following VB code uses Parallel.ForEach to pass the Delay subroutine the values 2, 3, 1:

   Dim secs() As Integer = {2, 3, 1}
   Parallel.ForEach(secs, AddressOf Delay)

The following code shows the C# version:

   int[] secs = {2, 3, 1};
   Parallel.ForEach(secs, Delay);

You can easily pass objects to the DelayWithData method instead of simple integers. In the following example, the DelayData class has an integer property called NumSeconds, used to determine the delay duration. This code provides the same effect as the previous examples—but uses objects. Here's the Visual Basic version:

   Dim delays() As DelayData = { _
       New DelayData With {.NumSeconds = 2}, _
       New DelayData With {.NumSeconds = 3}, _
       New DelayData With {.NumSeconds = 1} _
   Parallel.ForEach(delays, AddressOf DelayWithData)

Here's the C# equivalent:

   DelayData[] delays = {
       new DelayData{NumSeconds = 2},
       new DelayData{NumSeconds = 3},
       new DelayData{NumSeconds = 1}
   Parallel.ForEach(delays, DelayWithData);

The sample program RepeatedTasks demonstrates the Parallel.For and Parallel.ForEach methods. Whether the program uses Parallel.For or Parallel.ForEach, it takes almost exactly half as long on my dual-core system as it does when it executes its tasks sequentially.

Here's a more interesting demonstration of Parallel.ForEach. The program example called ForEachObjects calls the FilterImage subroutine four times, passing it objects of the PicInfo class. That class has two properties that contain input and output bitmaps. The FilterImage subroutine applies an embossing filter to the input image and saves the result in the output image. After the call to Parallel.ForEach finishes, the main program displays the results (see Figure 1).

Figure 1. Fascinating Filters: Program ForEachObjects uses Parallel.ForEach to create four embossed images in parallel.

The following code shows how the program uses Parallel.ForEach.

   Dim pic_infos() As PicInfo = { _
       New PicInfo(picOld1.Image, picNew1.Image), _
       New PicInfo(picOld2.Image, picNew2.Image), _
       New PicInfo(picOld3.Image, picNew3.Image), _
       New PicInfo(picOld4.Image, picNew4.Image) _
   Parallel.ForEach(pic_infos, AddressOf FilterImage)

If you look closely at Figure 1, you'll see that applying the filter to the images sequentially took about 2.63 seconds, but only about 1.60 seconds in parallel. This program has more overhead than the previous more trivial programs so the parallel calculation takes more than half as long as the sequential version; but the parallel version still saves about 40 percent of the total time.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date