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+

Limiting Parallelism with Parallel.Invoke

Parallel.Invoke can be used to launch several tasks that may execute in parallel. Depending upon the functionality of the tasks and their use of resources, it can be beneficial to limit the parallelism to a fixed number of concurrent tasks.

ParallelOptions

In a recent article I described the use of the MaxDegreeOfParallelism property of the ParallelOptions class when applied to parallel foreach and parallel for loops. Using an instance of this class with such a loop allows you to limit the number of concurrently executing loop iterations. This is important for algorithms that might use limited resources or that may call code with critical sections protected by lock statements. Without the limit, having too many concurrent processes can cause poorer performance than with fewer parallel tasks.

The same principle applies to the use of the Parallel.Invoke method, which allows you to run a set of action delegates with potential parallelism. Again, if these tasks could interfere with each other, or if they could use the entire processor capability at the expense of other software executing on the same computer, you should consider limiting the number of actions that may execute at the same time. As with loops, you can achieve this with the ParallelOptions class's MaxDegreeOfParallelism property.

Example

To demonstrate let's create a simple example. Create a new console application project and add the following two using directives to the class containing the Main method:

using System.Threading;
using System.Threading.Tasks;

For our first example we'll launch five actions without specifying a limit to the number of parallel tasks. It is quite possible that all five of the tasks will execute concurrently.

The code for the Main method and a supporting method is shown below. The Parallel.Invoke call passes five actions, each including a call to RunTask. RunTask outputs two messages with a five second pause between them. These messages show when each task starts and ends, allowing you to see the order of execution. You can see from the sample results that each of the five tasks started before any other completed; all five tasks ran concurrently. The results that you see may vary.

static void Main()
{
    Parallel.Invoke(
        () => RunTask(1),
        () => RunTask(2),
        () => RunTask(3),
        () => RunTask(4),
        () => RunTask(5)
        );
}

static void RunTask(int taskNumber)
{
    Console.WriteLine("Task {0} started", taskNumber);
    Thread.Sleep(5000);
    Console.WriteLine("Task {0} complete", taskNumber);
}

/* OUTPUT

Task 1 started
Task 2 started
Task 3 started
Task 4 started
Task 5 started
Task 1 complete
Task 2 complete
Task 4 complete
Task 3 complete
Task 5 complete

*/

To limit the parallelism we start by creating and initialising a ParallelOptions object in exactly the same manner as when working with parallel loops. The object is passed to the first parameter of the Invoke method.

In the sample below the code is limited to two concurrent tasks. You can see from the output that once the first two tasks have started the remaining tasks are blocked. As one executing task terminates, another starts. This continues until all of the actions have completed but at no time are more than two running at the same time.

static void Main()
{
    ParallelOptions po = new ParallelOptions();
    po.MaxDegreeOfParallelism = 2;
    Parallel.Invoke(po,
        () => RunTask(1),
        () => RunTask(2),
        () => RunTask(3),
        () => RunTask(4),
        () => RunTask(5)
        );
}

/* OUTPUT

Task 1 started
Task 2 started
Task 2 complete
Task 3 started
Task 1 complete
Task 4 started
Task 3 complete
Task 5 started
Task 4 complete
Task 5 complete

*/
30 April 2013