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.

Detecting an Exiting Loop

A better way to deal with exiting parallel loops with long-running iterations is to detect the need to stop and halt the remaining iterations as quickly as possible. You should identify that the loop will end, perform any clean-up operation to ensure the integrity of the software and terminate the remaining iterations as soon as possible.

To help with this, the ParallelLoopState object for a loop includes the ShouldExitCurrentIteration property. This Boolean value is usually false. It is set to true if you call Break or Stop, or if an unhandled exception occurs within the loop. Whenever possible in long loop iterations, you should check the property and exit gracefully if it is true.

To demonstrate, modify the code as shown below. Here the DoSlowProcess method has been changed so that the ParallelLoopState object is passed to a parameter. Before each 100ms pause, the ShouldExitCurrentIteration property is checked. If the property remains false for the entire method call, the completion message is outputted as before. If the property changes to true, a cancellation message is shown and the method returns, preventing further delay.

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

private static void DoSlowProcess(int value, ParallelLoopState pls)
{
    for (int i = 0; i < 10; i++)
    {
        if (pls.ShouldExitCurrentIteration)
        {
            Console.WriteLine("Item {0} CANCELLED AFTER {1}00ms", value, i);
            return;
        }
        Thread.Sleep(100);
    }

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

/* OUTPUT

Item 5 complete
Item 1 complete
Item 13 complete
Item 9 complete
Item 17 complete
Item 2 CANCELLED AFTER 100ms
Item 6 CANCELLED AFTER 100ms

*/

You can see from the comments that iteration 5 completed before iteration 1, which called Break. Iterations 13, 9 and 7 managed to reach completion, probably due to the program running on a processor with enough cores to run them in parallel with iteration 1. The final two messages show that two iterations completed early, each with 900ms of potential delay averted. Thirteen iterations did not start.

IsStopped and IsExceptional

There are two similar properties that you can use to detect that a loop is terminating early. These are IsStopped and IsExceptional. ShouldExitCurrentIteration is a general property, in that it is set to true when you call Break or Stop, or when an unhandled exception occurs. IsStopped and IsExceptional are more specific. IsStopped becomes true only after Stop is called. IsExceptional is true only when an unhandled exception is encountered.

You can use the three properties together to determine why a loop is terminating early. In some situations this will allow you to change the early exit behaviour according to the reason. The table below shows the common combinations of the three flags and their meaning.

ShouldExitCurrentIterationIsStoppedIsExceptionalSituation
falsefalsefalseThe loop is not terminating.
truefalsefalseBreak was called in an iteration.
truetruefalseStop was called in an iteration.
truefalsetrueAn iteration caused an unhandled exception
29 September 2013