Cancellation

As mentioned on the first page of this post, async operations may need to be stopped (e.g. results are not needed anymore). As we saw in the post about asynchronous techniques in .Net, for this to work, cooperation is required for a graceful handling of cancellation. With TPL cancellation is “politely” requested from the initiating call to the Task. Yet even though cancellation was requested the operation might be running to completion. As is the nature of cooperation.

CancellationToken & CancellationTokenSource

To coordinate the cancellation process TPL has the notion of a CancellationToken and a CancellationTokenSource. Where as the CancellationTokenSource is used by the requesting party, and the CancellationToken is injected into the requested task.

First the cancellation requesting code spawns the CancellationTokenSource, which in turn creates the CancellationToken. This is then supplied to the Task in question.

so an API would look like this:

//Synchronous:
void MyOperation();
//Asynchronous:
Task MyOperationAsync();
Task MyOperationAsync(CancellationToken token);

Note you have now two methods and return param of task. Those two methods are necessary because not every caller might need cancellation. Shortly we will see how we can solve this with only one operation.

Now to cancel a method you simply create a Token with the CancellationTokenSource and provide it as the argument to the async method. One Token can be utilized for several different methods, if all should be cancelled together. But a token is only good for one cancellation request and cannot be reused. So you can employ it for different Tasks, but only request it once (but then for all Tasks that were injected with the Token). Or in other words, there is no Reset method on a CancellationToken, once employed it is used up.

To Cancel, simply call the Cancel operation on the CancellationTokenSource.

public static void MyOperationInvoke()
{
var cts = new CancellationTokenSource();
var token = cts.Token;
var myTask = MyOperationAsync(token);
while (!myTask.IsCompleted)
{
if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Q
{
cts.Cancel();
}
Thread.Sleep(250);
}
}

This method cancels the MyOperationAsync if the “Q” Key is pressed and did not run to completion.

But aside from invoking this, you need to cooperate to the cancellation in the method that gets the token. This can be done before or during execution of async method. Creating a task does not mean immediate execution, as a unit of asynchronous work it could be pending in the queue of a thread pool thread. Therefore it can be canceled before the task actually runs.

var cts = new CancellationTokenSource();
var token = cts.Token;
Task.Factory.StartNew(() => AnyAsync(), token);

During execution, the task has to decide when it is safe to act on the cancellation request of the token.

You could gracefully exit like so:

if (token.IsCancellationRequested) 
return;

Yet you would have no way to know if the cancellation actually put through. Therefore the standard way to use cancellation is to throw an OperationCanceledException(“Canceled”, token);

if (token.IsCancellationRequested) 
throw new OperationCanceledException("Canceled", token);
//This seems like boilerplate, therefor exists the 
token.ThrowIfCancellationRequested();

With this the Tasks state is Canceled. Note that the token that is supplied as an argument, needs to be provided by the cancelationtokensource that requests the Cancelation, else the resulting state of the Task will be Faulted instead of Canceled.

Cancellation can be supplied with a timeout (which is most often good practice in async programming), so it throws automatically with .Net 4.5 CancelAfter. As I mentioned before you use one method instead of two and if you do not need cancellation simply supply the CancellationToken.None. (Null pattern)

Progress

The TPL also supports progress reporting. Mostly progress is represented by a percentage, yet the TPL API makes it possible to implement your own style (e.g. show the current components during an installation). TPL offers the following interface:

public interface IProgress<T>
{
void Report(T value);
}

To report progress you can call the Report method inside your asynchronous operation. For consuming the Progress<T> either implement the IProgress interface or use the ready to use Progress class from TPL.

public class MyProgressType
{
string ComponentName{get; set;}
}
public static Task MyOperationAsync(CancelationToken ct, IProgress<MyProgressType> progress)
{
return Task.Factory.StartNew(() => 
{
var componentName = "Next Component";
progress.Report(new MyProgressType { ComponentName = componentName});
ct.ThrowIfCancellationRequested();
}, ct);
}

The Progress class supports either Event Subscription on the one hand or delegate invocation on the other hand for the consumption of the progress.

var cts = new CancellationTokenSource();
var task = MyOperationAsync(cts.Token, new Progress<MyProgressType>(myType => Console.WriteLine(myType.ComponentName));
//or 
var cts = new CancellationTokenSource();
var progress = new Progress<MyProgressType>();
progress.ProgressChanged += (s, e) => Console.WriteLine(e.ComponentName);
var task = MyOperationAsync(cts.Token, progress);

Task Relationships

The parts we looked at until now, viewed a task as a unit of async work as “island” of activity. That is, a task did some work and then when it is done, it frees the thread and the resources it occupied. To get relationships working between tasks you can chain tasks together to arrange parent-child relationships.

Chaining Tasks

TPL allows for tasks that have the state of WaitingForActivation. If all antedecent tasks have completed, it transfers into WaitingToRun state once all antecedent tasks have completed. To chain tasks together, you use the ContinueWith method:

var task = Task.Factory.StartNew(() => Console.WriteLine("first task"));
var sndTask = task.ContinueWith(fristTask => Console.WriteLine("second task"));

ContinueWith has a parameter, this allows for supplying data from one task to another.

Conditional/Unconditional

The above code is unconditional chaining of tasks. This can be changed with TaskContinuationOptions.

[Flags]
public enum TaskContinuationOptions
{
AttachedToParent = 4,
DenyChildAttach = 8,
ExecuteSynchronously = 0x80000,
HideScheduler = 0x10,
LazyCancellation = 0x20,
LongRunning = 2,
None = 0,
NotOnCanceled = 0x40000,
NotOnFaulted = 0x20000,
NotOnRanToCompletion = 0x10000,
OnlyOnCanceled = 0x30000,
OnlyOnFaulted = 0x50000,
OnlyOnRanToCompletion = 0x60000,
PreferFairness = 1,
RunContinuationsAsynchronously = 0x40
}

So if you only would want that a task is run if the one before it ran to completion you would specify this in the following manner:

var task = Task.Factory.StartNew(SomeMethodThatMightFail)
.ContinueWith(x => Console.WriteLine(x.Exception), TaskContinuationOptions.OnlyOnRanToCompletion);

Why continuations

Now that we know how to chain tasks, there is the question of why one wants to do that. It seems odd at first, why not simply run all of the code in a single task. But it makes sense for I/O bound Tasks, that need to wait on the completion of the task before.
Also continuation is a good technique to minimize the number of active threads in use, which when you remember the way the thread pool works, minimizes resource consumption.

You can also combine the result of multiple Tasks to one with ContinueWhenAll and ContinueWhenAny methods.

var multipleTasks = new Task[3];
for (i = 0; i < 3; i++)
{
multipleTasks[i] = Task.Factory.StartNew(SomeWork);
}
Task.Factory.ContinueWhenAll(multipleTasks, antedecentTasks => CombineTheResults(antedecentTasks));

Nested and child Tasks

Nested tasks and child tasks are pretty similar, both are created when you create other tasks during an execution of a Task.
The difference is, that nested task have no bearing on the Task that creates them, except for that this unit of work is directly employed to the work stealing queue of the creating Task. Child tasks on the other hand make sure that the parent task only is completed, if all child tasks are completed. Also all unhandled exceptions from a child Task are propagated to the parent. (see section with error handling)

To create a child task you supply the TaskCreationOption of AttachedParent:

Task.Factory.StartNew(() => 
{
var child = Task.Factory.StartNew(() => {/*something something*/, TaskCreationOptions.AttachedToParent);
});

You can also prevent tasks from becoming children with DenyChildAttach:
This makes a Task forcefully a nested Task instead of a child task.

Categories: TPL

0 Comments

Leave a Reply

Avatar placeholder