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+

Cancelling Slow Parallel Loop Iterations

Parallel loops can be stopped early due to an exception being thrown or by calling the Break or Stop methods of the linked ParallelLoopState object. These actions do not cancel any scheduled iterations, which can be problematic for slow processes.

Parallel Loops

When using the Task Parallel Library's looping features you can easily generate loops that are similar to for or foreach, except that multiple iterations may execute concurrently. When running on a computer with more than one processor core, a parallel for or parallel foreach loop can provide improved performance over the equivalent sequential loop.

One reason that you might elect to use a parallel loop is that you need to execute a slow calculation many times. If the process is mainly limited by the speed of the processor, it might be much faster to divide the iterations between multiple processor cores. The speed increase is lower if the iterations are limited by factors such as disk access. It is possible that a parallel loop can be slower than a sequential version when such a shared resource is used.

One of the key differences between parallel and sequential loops is the behaviour when the loop is cancelled, or when an exception is thrown. In a sequential loop you can execute the break statement to immediately exit. An unhandled exception will also stop the loop. With parallel loops the behaviour is different. You can exit a parallel loop using the ParallelLoopState class's Break or Stop methods. An exception will also terminate the loop early. However, any parallel iterations that have already started or that have been scheduled will still execute. Only unscheduled iterations will be cancelled entirely.

If the code within a loop iteration is slow-running, perhaps taking seconds or minutes to complete, it might be a problem to allow individual iterations to continue when the loop is stopping. Handily, the ParallelLoopState object includes three properties that you can use to detect such situations and, perhaps, terminate the remaining iterations quickly.

We'll look at these three properties in the sections below. To reference the correct namespaces for the classes used, ensure that you include the following two using directives:

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

Example Loop

Let's start by looking at an example parallel loop that terminates early. The loop in the Main method below could execute up to twenty iterations. Each calls the DoSlowProcess method, which emulates a long-running activity by sleeping for one hundred milliseconds ten times before outputting a message.

Although twenty iterations are possible, they are unlikely because when the loop control variable, "i", is one, the ParallelLoopState variable's Break method is called. This tells the loop to stop. In a sequential loop we would see a single message. In the parallel loop we will see several for two reasons. The first is that any iterations that have started, or that have been scheduled for execution, will still run. The second reason is that it is possible that the first iteration to execute may not be the one where "i" is one.

Try running the code several times to see the results. It is likely that the results will differ between runs, unless you are using a computer with a single core. One example output is shown in the comment.

static void Main(string[] args)
{
    Parallel.For(1, 21, (i, pls) =>
    {
        DoSlowProcess(i);
        if (i == 1)
        {
            pls.Break();
        }
    });
}

static void DoSlowProcess(int value)
{
    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(100);
    }

    Console.WriteLine("Item {0} complete", value);
}

/* OUTPUT

Item 5 complete
Item 9 complete
Item 1 complete
Item 17 complete
Item 13 complete
Item 2 complete
Item 6 complete
Item 10 complete

*/

In the example output you can see that eight iterations completed, rather than just the one that would have run with a sequential loop. The iterations for the values 5 and 9 completed before the one that exited the loop. Iterations 17, 13, 2, 6 and 10 had either started or were scheduled before the Break method was encountered. The other twelve possible iterations were never scheduled so did not run.

In this example the impact is quite low. However, if this was a process instigated by a user and the iterations took minutes to execute, rather than seconds, this could lead to unacceptable delays. The user should not need to wait for such a long time after a loop is halted or an exception causes it to stop.

29 September 2013