Part 1 of this series introduced the async patterns and introduced cancellation with the BackgroundWorker class as it existed since .NET 2. This part shows how the new .NET 4 cancellation framework can be used.
Previously to .NET 4, cancellation with async method calls was implemented in different ways if it was supported at all. For example, the BackgroundWorker implements cooperative cancellation by invoking the CancelAsync method, the long-running method needs to verify if it should be canceled by checking the CancellationPending property and needs to cancel by setting the Cancel property of the DoWorkEventArgs. More about this in the previous blog entry.
.NET 4 now supports cooperative cancellation of async methods in a standard way. The heart of this new framework is the CancellationToken struct. The CancellationToken is created by a CancellationTokenSource. This token can then be passed to any activity that should be cancelled. In case of a cancellation, the async call can verify cancellation by checking the IsCancellationRequested property of the CancellationToken. This unified model is now available with several classes offering asynchronous requests.
Cancellation of a Parallel Loop
The first example demonstrates cancelling a parallel for loop. Parallel.For is new with .NET 4 for doing iterations in parallel. In the sample the loop is cancelled from a separate task after 500 ms.
First a CancellationTokenSource is created. The separate task to cancel the loop invokes the Cancel method of the CancellationTokenSource to cancel the task(s) that have the cancellation token associated.
The Parallel.For method does a parallel loop from 0 to 100 and invokes the method that is defined by the fourth parameter that is of type Action<int>. The parameter that is received with the Action<int> delegate is the index of the loop. The implementation of this method just loops with a normal for and does a small sleep. At the begin and the end of the implementation a start and finish message is written so it can be seen if the iteration was completed.
With the third parameter ParallelOptions are passed where the CancellationToken property is assigned to the CancellationToken from the CancellationTokenSource. This makes it possible to cancel the loop.
In case of a cancellation, an exception of type OperationCanceledException is thrown.
var cts = new CancellationTokenSource(); // start a task that sends a cancel after 500 ms new Task(() => { Thread.Sleep(500); cts.Cancel(false); }).Start(); try { ParallelLoopResult result = Parallel.For(0, 100, new ParallelOptions { CancellationToken = cts.Token }, x => { Console.WriteLine("loop {0} started", x); int sum = 0; for (int i = 0; i < 100; i++) { Thread.Sleep(2); sum += i; } Console.WriteLine("loop {0} finished", x); }); } catch (OperationCanceledException ex) { Console.WriteLine(ex.Message); }
With the parallel for when a cancellation occurs all iterations hat have been started can continue to the end. However, new operations are not started anymore. This can easily be verified by checking the output as shown here.
loop 0 started loop 50 started loop 50 finished loop 51 started loop 0 finished loop 1 started loop 1 finished loop 2 started loop 51 finished loop 52 started loop 2 finished loop 52 finished The operation was canceled.
Cancellation of a Task
The parallel loop can do the cooperative cancellation on its own. This is not possible with an asynchronous running task as there’s no way to know when the task can be cancelled without knowing the method that is called. The implementation of the task method needs to cancel itself in case a cancellation is requested.
Similar as in the previous sample again a CancellationTokenSource is created. The CancellationToken that is associated with the CancellationTokenSource is passed on creation of the TaskFactory. Alternatively a CancellationToken can also be passed on creating a new task. The task that is created from the TaskFactory has an implementation that verifies if cancel should be done by checking the IsCancellationRequested property from the CancellationToken. In case cancel should be done an exception of type TaskCanceledException is thrown with the help of the method ThrowIfCancellationRequested. The type TaskCanceledException derives from the base class OperationCanceledException. The exception can be caught by someone waiting on the task.
var cts = new CancellationTokenSource(); // start a task that sends a cancel after 500 ms new Task(() => { Thread.Sleep(500); cts.Cancel(); }).Start(); var factory = new TaskFactory(cts.Token); Task t1 = factory.StartNew(new Action<object>(f => { Console.WriteLine("in task"); for (int i = 0; i < 20; i++) { Thread.Sleep(100); CancellationToken ct = (f as TaskFactory).CancellationToken; if (ct.IsCancellationRequested) { Console.WriteLine("cancelling was requested"); ct.ThrowIfCancellationRequested(); break; } Console.WriteLine("in loop {0}", i); } Console.WriteLine("task finished"); }), factory); try { t1.Wait(); } catch (AggregateException ex) { foreach (var innerEx in ex.InnerExceptions) { Console.WriteLine(innerEx.Message); } }
It’s always the same. No matter what asynchronous operations are available, they should be cancelable with the help of a CancellationToken. That’s one of the great new features of .NET 4.
More about asynchronous programming with .NET 4 in my book Professional C# 4 with .NET 4 and in my .NET 4 workshop.
Christian
Comments