TPL – Continuation

2011/12/28

no comments

TPL – Continuation

this post will discuss TPL Continuation.

Tpl, Continuation, continuewith,task

TPL continuation can chain task into a pipeline.

when dealing with dependencies between parallel work units, like [encoding -> compression -> encryption], continuation is the API for scheduling work unit upon completion of other work unit.

the general idea is quit similar to the old APM pattern (BeginXxx, EndXxx) callback.

basic completion

the syntax of continuation:

Code Snippet
  1. Task tsk = Task.Factory.StartNew(() => {/* do somethng*/});
  2. tsk.ContinueWith(t => {/* continue when something complete*/});

continuation API is fairly straight-forward:

we can define a continuation action upon task completion (the t in the lambda represent the completed task).

completion of Task<T>

continuation does also support Task<T> this way we can handle a task result upon the task’s completion.

the following sample show the concept of the [encoding -> encryption -> send] pipeline:

Code Snippet
  1. Task<byte[]> tskEncoding = Task.Factory.StartNew(() =>
  2. {
  3.     return Encoding.UTF8.GetBytes(data);
  4. });
  5. Task<Byte[]> tskEncrypt  = tskEncoding.ContinueWith(t =>
  6. {
  7.     return Encrypt(t.Result);
  8. });
  9. Task tskSend = tskEncoding.ContinueWith(t =>
  10. {
  11.     Send(t.Result);
  12. });

the first task (line 1-4) return (async) encoding data.

the first continuation task (line 5-8) get the encoded data from the result of the completed task and encrypt it, the encrypted data return as the task’s result.

the second continuation (line 9-12) will be schedule on the completion of the encryption task.

multiple completions

completion API does not limit to single completion per a task. the completion represent a callback and we can set as many callback as we need for any Task (or Task<T>).

Code Snippet
  1. Task tsk = Task.Factory.StartNew(() => {/* do somethng*/});
  2. tsk.ContinueWith(t => {/* callback 1 */});
  3. tsk.ContinueWith(t => {/* callback 2 */});

Continue when all/any

we can also set continuation which will be trigger upon the completion of multiple tasks.

Code Snippet
  1. Task tsk1 = Task.Factory.StartNew(() => {/* do somethng */});
  2. Task tsk2 = Task.Factory.StartNew(() => {/* do somethng else */});
  3. Task[] tsks = new Task[] { tsk1, tsk2 };
  4. Task.Factory.ContinueWhenAll(tsks, tskArr => {/* callback 1 */});

or continue on the completion of the first among multiple tasks.

Code Snippet
  1. Task tsk1 = Task.Factory.StartNew(() => {/* do somethng */});
  2. Task tsk2 = Task.Factory.StartNew(() => {/* do somethng else */});
  3. Task[] tsks = new Task[] { tsk1, tsk2 };
  4. Task.Factory.ContinueWhenAny(tsks, firstTask => {/* callback 1 */});

Parent / Child

as you may know TPL support a parent / child execution model,
when you start a task within an executing task scope you can set the task behavior to accept the parent / child paradigm.
the TPL infrastructure does aware when a task is having children and behave accordantly (wait will wait for the completion of all the task’s children, cancelling a parent task will affect all of its children, the debug parallel tasks window can present the task’s hierarchic).

Code Snippet
  1. Task.Factory.StartNew(() =>
  2. {
  3.     Task.Factory.StartNew(() =>
  4.         {
  5.             // …
  6.         },TaskCreationOptions.AttachedToParent);
  7.     // …
  8. });

Parent / Child and continuation

when it come to continuation the continuation callback will occurs only after the completion of all the task’s children.

Code Snippet
  1. var t = Task.Factory.StartNew(() =>
  2. {
  3.     Task t1 = Task.Factory.StartNew(() =>
  4.     {
  5.         Thread.Sleep(1000);
  6.         Console.WriteLine("child1");
  7.     }, TaskCreationOptions.AttachedToParent);
  8. });
  9. t.ContinueWith(tsk => Console.WriteLine("Complete !!!"));

the above code demonstrate a simple continuation upon parent child task.

the completion will occurs when after the completion of t1.

Parent / child with nested continuation

let take another scenario when both the parent and the child task is having a continuation.

Code Snippet
  1. var t = Task.Factory.StartNew(() =>
  2. {
  3.     Task t1 = Task.Factory.StartNew(() =>
  4.     {
  5.         Thread.Sleep(1000);
  6.         Console.WriteLine("child1");
  7.     }, TaskCreationOptions.AttachedToParent);
  8.     t1.ContinueWith(tsk =>
  9.         {
  10.             Thread.Sleep(1000);
  11.             Console.WriteLine("child continuation");
  12.         });            
  13. });
  14.  
  15. t.ContinueWith(tsk => Console.WriteLine("parent continuation"));

let think of the above code. will the parent continuation complete before or after the child continuation?

the answer is: the parent continuation will ignore the child continuation and complete first.

the parent continuation will be aware of the child continuation only if we mark the child continuation with TaskContinuationOptions.AttachedToParent.

Code Snippet
  1. var t = Task.Factory.StartNew(() =>
  2. {
  3.     Task t1 = Task.Factory.StartNew(() =>
  4.     {
  5.         Thread.Sleep(1000);
  6.         Console.WriteLine("child1");
  7.     }, TaskCreationOptions.AttachedToParent);
  8.     t1.ContinueWith(tsk =>
  9.         {
  10.             Thread.Sleep(1000);
  11.             Console.WriteLine("child continuation");
  12.         }, TaskContinuationOptions.AttachedToParent);            
  13. });
  14.  
  15. t.ContinueWith(tsk => Console.WriteLine("parent continuation"));

now the parent continuation will complete after the completion of the child’s task continuation.

Conditional continuation

till now we have seen many of the continuation scenarios. the last scenario which I want to present is the cool ability of tuning the continuation to occurs only when the execution status end with specific condition.

you can set the continuation to occur only on success, failure or cancellation.

Code Snippet
  1. var cancellation = new CancellationTokenSource();
  2. Task t = Task.Factory.StartNew(() =>
  3.     {
  4.         if (Environment.TickCount % 2 == 0)
  5.             throw new Exception();
  6.         else
  7.             Console.WriteLine("pass");
  8.     }, cancellation.Token);
  9.  
  10. t.ContinueWith(tsk => Console.WriteLine("OK"),
  11.     TaskContinuationOptions.OnlyOnRanToCompletion);
  12. t.ContinueWith(tsk => Console.WriteLine("Cancelled"),
  13.     TaskContinuationOptions.OnlyOnCanceled);
  14. t.ContinueWith(tsk => Console.WriteLine("Failed"),
  15.     TaskContinuationOptions.OnlyOnFaulted);

the TaskContinuationOptions is a bitwise so you can specify multiple option upon single continuation.

Summary

continuation is one of the most powerful feature of the new TPL infrastructure.

it is having more feature and it simpler to use than the old APM pattern.

using the continuation pattern we can manage complex parallelism with regard of dependencies.

finally ,as I will describe in latter past, the new async  feature (of .NET 4.5 / C#5) is all about the continuation concept.

Shout it

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>