Task Parallel Library 101 Using C#

Task Parallel Library (TPL) provides a level of abstraction to help us to be more effective as developers/programmers when it comes to parallelism. Knowing at least the basics are beneficial. In that case, let's give it a try.

Introduction

 
Most developers are a bit reluctant when it comes to the practice of parallelism and/or concurrency. Probably, because they have the impression that parallelism is complex, especially when it comes to implementation and debugging. Additionally, probably because developers are most likely familiar with synchronous code style since most applications are written that way.
 
Nevertheless, Task Parallel Library (TPL) comes to the rescue and knowing at least the basic usage of this library gives a lot of benefits to developers and alike.
 
In this article, we are going to explore the world of Task Parallel Library of .NET Framework using C#. We are going to tackle the following,
  • Task Parallel Library (TPL) and its benefits
  • Create a Task and Start and Wait to Finish
  • Create a Task with Cancellation
  • Create a Task with Continuation

Task Parallel Library(TPL) and its benefits

 
Task Parallel Library (TPL), basically provides a higher level of abstraction. Fundamentally, it boils down to a “task” which is equivalent to a thread except that it is more lightweight and comes without the overhead of creating an OS thread. In other words, a task is an easier way to execute something asynchronously and in parallel compare to a thread. The main goal is to give the developers the opportunity to add parallelism and/or concurrency to their applications. Moreover; the library gives the developer an idea of what's happening in the current task such as knowing the status, returned values, did the cancellation happened? , did an exception thrown and/or handled? and continuation. 
 
Getting Started
 
TPL is a set of APIs using the System.Threading and System.Threading.Task namespace.
 
Meaning, anytime dealing with TPL APIs, you need to refer to these namespaces on top of your class filename. 
  1. using System.Threading.Tasks;  

Create a Task and Start and Wait to Finish

 
In this section, we are going to create on how to create a Task.
 
Create a task using an action void delegate,
  1. var action = new Action(() =>   
  2. {   
  3.     Task.Delay(5000);   
  4.     Console.WriteLine("Hello Task World");   
  5. });  
  6.   
  7. Task myTask = new Task(action);  
  8. myTask.Start();  
  9.   
  10. myTask.Wait();   

Create a task using an action delegate and passes an argument,

  1. var action = new Action<int>((n) =>   
  2. {  
  3.     for (int i = 0; i < n; i++)  
  4.     {  
  5.         Task.Delay(5000);  
  6.         Console.WriteLine($"{i}");  
  7.     }  
  8. });  
  9.   
  10. Task myTask = new Task(() => { action(500); });  
  11. myTask.Start();  
  12.   
  13. myTask.Wait();   
Create a task using a func delegate,
  1. var func = new Func<intint>((n) =>   
  2. {  
  3.     int total = 0;  
  4.   
  5.     for (int i = 0; i < n; i++)  
  6.     {  
  7.         Task.Delay(5000);  
  8.   
  9.         total += (int)Math.Pow(i, 2);  
  10.   
  11.         Console.WriteLine($"{total}");  
  12.     }  
  13.   
  14.     return total;  
  15. });  
  16.   
  17. Task<int> myTask = new Task<int>(() => { return func(50); });  
  18. myTask.Start();  
  19.   
  20. myTask.Wait();  
  21.   
  22. Console.WriteLine($"My total {myTask.Result}");  

Create a Task with Cancellation

 
If you are interested in creating a task alongside a cancellation feature. First, you need to define and instantiate a cancellation token. Then, if the cancellation token object is defined and ready, you can pass it before starting the task. For your method-delegate, it is obvious you need to define the cancellation token as a parameter in order to detect if the cancellation has been requested. Let us an example below.
  1. using System;  
  2. using System.Threading;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace Console_App_Task_With_Cancellation  
  6. {  
  7.     class Program  
  8.     {  
  9.         private static int Fibonacci(int n, CancellationToken token)  
  10.         {  
  11.             int a = 0;  
  12.             int b = 1;  
  13.   
  14.             for (int i = 0; i < n; i++)  
  15.             {  
  16.                 if (token.IsCancellationRequested)  
  17.                 {  
  18.                     Console.WriteLine("Getting out of the Fibonacci Sequence ....");  
  19.   
  20.                     return a;  
  21.                 }  
  22.   
  23.                 int temp = a;  
  24.                 a = b;  
  25.                 b = temp + b;  
  26.   
  27.                 Console.WriteLine($"Current result {a}");  
  28.             }  
  29.   
  30.             return a;  
  31.         }  
  32.   
  33.         static void Main(string[] args)  
  34.         {  
  35.             ConsoleKeyInfo input;  
  36.   
  37.             CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();  
  38.   
  39.             CancellationToken token = cancellationTokenSource.Token;  
  40.   
  41.             Task<int> myFibonnaciTask = null;  
  42.   
  43.             myFibonnaciTask = new Task<int>(() => { return Fibonacci(5000, token); }, token);  
  44.   
  45.             myFibonnaciTask.Start();  
  46.   
  47.             do  
  48.             {  
  49.                 while(!Console.KeyAvailable)  
  50.                 {  
  51.                     if (myFibonnaciTask.IsCompleted)  
  52.                     {  
  53.                         goto End;  
  54.                     }  
  55.                 }    
  56.   
  57.                 input = Console.ReadKey(true);  
  58.   
  59.                 if (!myFibonnaciTask.IsCompleted & !cancellationTokenSource.IsCancellationRequested)  
  60.                 {  
  61.                     cancellationTokenSource.Cancel();  
  62.   
  63.                     Console.WriteLine("Cancelled");  
  64.                 }  
  65.   
  66.             } while (input.Key != ConsoleKey.Escape);  
  67.   
  68. End:  
  69.             Console.WriteLine($"Result {myFibonnaciTask.Result}");  
  70.             Console.WriteLine("Program has ended!");  
  71.             Console.ReadKey();  
  72.   
  73.         }  
  74.     }  
  75. }  
The major key points here are the Cancel method and IsCancellationRequested property. The Cancel methods say what it does and IsCancellationRequested property checks whether cancellation has been made. 
 

Create a Task with Continuation

 
Let us see first an example, then let's start from that. 
  1. var func = new Func<intint>((n) =>  
  2. {  
  3.     int total = 0;  
  4.   
  5.     for (int i = 0; i < n; i++)  
  6.     {  
  7.         Task.Delay(5000);  
  8.   
  9.         total += (int)Math.Pow(i, 2);  
  10.   
  11.         Console.WriteLine($"{total}");  
  12.     }  
  13.   
  14.     return total;  
  15. });  
  16.   
  17. Task<int> myTask1 = new Task<int>(() => { return func(50); });  
  18.   
  19. Task<int> myTask2Square =  
  20.     myTask1.ContinueWith((myTask) =>  
  21.     {  
  22.         return (int)Math.Sqrt(myTask.Result);  
  23.     }, TaskContinuationOptions.OnlyOnRanToCompletion);  
  24.   
  25.   
  26. myTask1.Start();  
  27.   
  28. Task.WaitAll(myTask1, myTask2Square);  
  29.   
  30. Console.WriteLine($"First result: {myTask1.Result}");  
  31.   
  32. Console.WriteLine($"second result: {myTask2Square.Result}");  
A few points to mention, the above code sample. ContinueWith method, use this once you have decided to continue with another task. After the previous task completion.
 
In our example above, I used TaskContinuationOptions.OnlyOnRanToCompletion because I'm expecting it to be completed without any exceptions thrown else I used TaskContinuationOptions.OnlyOnFaulted. 
 
For complete usage of the TaskContinuationOptions, please see the table below. 
 
TaskContinuationOption
Usage
None
When no continuation options are specified.
PreferFairness
A hint to a System.Threading.Tasks.TaskScheduler object to schedule tasks.
LongRunning
Continuation will be long-running.
AttachedToParent
Specifies that the continuation is attached to a parent.
DenyChildAttach
The attached child task will not be able to attach to the parent task and will execute instead as a detached child task.
HideScheduler
LazyCancellation
Prevents completion of the continuation until the antecedent has completed.
RunContinuationsAsynchronously
Continuation task should be run asynchronously
NotOnRanToCompletion
The task should not be scheduled if its antecedent ran to completion.
NotOnFaulted
The task should not be scheduled if its antecedent threw an unhandled exception.
OnlyOnCanceled
Scheduled only if its antecedent was canceled.
NotOnCanceled
The task should not be scheduled if its antecedent was canceled.
OnlyOnFaulted
Scheduled only if its antecedent threw an unhandled exception.
OnlyOnRanToCompletion
Scheduled only if its antecedent ran to completion.
ExecuteSynchronously
Continuation task should be executed synchronously.
 
Create a Task with Continuation and Exception Handling,
  1. using System;  
  2. using System.Threading.Tasks;  
  3.   
  4. namespace Console_App_Task_With_Continuation_2  
  5. {  
  6.     class Program  
  7.     {  
  8.         static Random random = new Random();  
  9.           
  10.         static void Main(string[] args)  
  11.         {  
  12.             Task<int> mytask = new Task<int>(() =>  
  13.             {  
  14.                 Task.Delay(5000);  
  15.                 int result = (random.Next(1, 100) % 2);  
  16.   
  17.                 if (result == 0)  
  18.                 {  
  19.                     throw new Exception("Just a random exception");  
  20.                 }  
  21.                 else  
  22.                 {  
  23.                     return random.Next(1, 100);  
  24.                 }  
  25.             });  
  26.   
  27.             mytask.ContinueWith((task) =>  
  28.             {  
  29.                 Console.WriteLine($"Continue with these method when there is an exception. Message:{task.Exception.Message}");  
  30.   
  31.             }, TaskContinuationOptions.OnlyOnFaulted);  
  32.   
  33.             mytask.ContinueWith((task) =>  
  34.             {  
  35.                 Console.WriteLine("Task done");  
  36.   
  37.             }, TaskContinuationOptions.OnlyOnRanToCompletion);  
  38.   
  39.             try  
  40.             {  
  41.                 mytask.Start();  
  42.   
  43.                 Console.WriteLine($"{mytask.Result}");  
  44.             }  
  45.             catch (AggregateException aex)  
  46.             {  
  47.                 foreach (var e in aex.Flatten().InnerExceptions)  
  48.                 {  
  49.                     if (e is Exception)  
  50.                     {  
  51.                         Console.WriteLine(e.Message);  
  52.                     }  
  53.                 }  
  54.             }  
  55.         }  
  56.     }  
  57. }  
The example code above uses the Random object and checks whether the value is odd or even. Then finally, it either throws an exception or finished the task.
In our case, we have two continuation task which we can call antecedents and set its usage. These two antecedents have a specific job, the first one handled the exception and second handled the successful task execution.
 

Summary

 
In this article, we have discussed the following,
  • What's the Task Parallel Library(TPL) and some of its benefits? 
  • Not only that, we have seen the basic usage of Task
  • Usage of a cancellation 
  • Usage of continuation
  • Lastly, continuation and exception handling.
I hope you have enjoyed this article, as I have enjoyed it while writing. You can also find the sample code here at GitHub. Till next time, happy programming!