About
In this code snippet, we will take a look at the TPL(Task Parallel Library) in C#.
If you read some of my other posts on threading you will know that it can be quite complicated and laborious for the developers to implement parallelism into their programs(deadlocks, cancellation, thread pooling, getting data back from the thread, …). The Task Parallel Library(TPL) takes care of a lot of these things and or makes them easier to manage.
For example:
The TPL uses a thread pool so you don’t have to worry about using it.
The .NET runtime will also automatically decide how many threads it should create. The fact that this is automatic is great because if you don’t create enough threads your application won’t run as fast as it possibly could. While creating too many threads can actually slow down your application(too much context switching).
A Task can be thought of as a “unit of work”. You can take a method and “make it/put it” into a Task. This task can be run asynchronously using async/await(like shown in this post) and then be awaited at a later point in your code allowing you to execute other code in the meantime. Or you can make multiple tasks and execute them in parallel and wait for the results from them like we’ll do in this post.
Note:
For simple things, for example: looping through an array with a 100 entries, you shouldn’t use multithreading as it just increases the complexity of your code and won’t offer much performance gain(might even decrease it if used improperly).
Let’s have a look at the code below to see how to use the task parallel library.
Code:
using System; using System.Threading; using System.Threading.Tasks; namespace TPL { class Program { static void Main(string[] args) { Console.WriteLine("Start executing tasks."); #region 1. Creating tasks //1.1 //Create a task. Task t1 = new Task(() => doWork(1)); //Start the task. t1.Start(); //1.2 //Create and start a task at the same time. Task t2 = Task.Factory.StartNew(() => doWork(2)); //As a side note: //Here you can see a practical implementation of the factory software design patern. //Instead of instantiating the Task class it gets made in the "factory" and is then returned to us. //1.3 //This is how you can chain/schedule antother task after the previous one is finished. Task t3 = Task.Factory.StartNew(() => doWork(3)).ContinueWith((task) => doWork(4)); //1.4 Task<int> t4 = Task<int>.Factory.StartNew(() => doWork()); //You can access the result of a completed task by accessing the "Result" property. Console.WriteLine("Task 4 result: " + t4.Result); #endregion #region 2. Waiting for tasks //4. //As you might have noticed in the console output "Done executing tasks." gets written out before any of the tasks have finsihed. //Well that is great in the sense that we don't have any thread blocking code. But what if we need to wait for a task to complete before continuing(like in the example above)? //You can pass an array of the tasks that you want to wait to finish to the AwaitAll() method. //Now task 1 and 2 will execute before "Done executing tasks." is printed to the console. Task[] tasks = { t1, t2 }; Task.WaitAll(tasks); #endregion Console.WriteLine("Done executing tasks."); #region 5. Parellel for loops //Make some int arrays. int[] ar1 = new int[4] { 1, 5, 6, 8 }; int[] ar2 = new int[4] { 7, 5, 5, 8 }; int[] ar3 = new int[4] { 2, 3, 6, 3 }; //Make an array of int arrays. int[][] arrays = new int[][] { ar1, ar2, ar3 }; //Get and write out the sum for each array using For. (asynchronously) Parallel.For(0, 3, (i) => Console.WriteLine(sumArray(arrays[i]))); //Get and write out the sum for each array using ForeEach. (asynchronously) Parallel.ForEach(arrays, (array) => Console.WriteLine(sumArray(array))); #endregion #region 6. Parallel Invoke //Using Parallel.Invoke you can start up multiple prallel tasks at once. Parallel.Invoke( () => doWork(5), () => doWork(6), () => doWork(7) ); #endregion #region 7. Cancelation token. //If we want the ability to cancel a task we can implement a cancellation token to achive this. //Make a cancellation token. var cancel = new CancellationTokenSource(); try { //Start a task chain. Task t5 = Task.Factory.StartNew(() => doWork(5, cancel.Token)).ContinueWith((task) => doWork(6, cancel.Token)).ContinueWith((task) => doWork(7, cancel.Token)); //Simulate doing something ... Thread.Sleep(4500); /////////////////////////////// //We decide that our tasks need to be canceled. cancel.Cancel(); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { cancel.Dispose(); } #endregion Console.ReadLine(); } //Sums all the integers in na int array. public static int sumArray(int[] array) { int sum = 0; foreach (int element in array) sum += element; return sum; } public static void doWork(int taskNumber) { Console.WriteLine("Task " + taskNumber + " has started."); //Simulate doing time consuming work. Thread.Sleep(4000); Console.WriteLine("Task " + taskNumber + " is done."); } public static int doWork() { //Simulate doing time consuming work. Thread.Sleep(4000); int result = 10; return result; } //This overloaded version of the doWork() method takes in a cancelation token. public static void doWork(int taskNumber, CancellationToken cancel) { //If the cancelation tooken was sent the task will cancel. if (cancel.IsCancellationRequested) { Console.WriteLine("Task " + taskNumber + " canceled."); cancel.ThrowIfCancellationRequested(); } Console.WriteLine("Task " + taskNumber + " has started."); //Simulate doing time consuming work. Thread.Sleep(4000); Console.WriteLine("Task " + taskNumber + " is done."); } } }