C# Task Parallel Library: High Performance .Net Code
Task Parallel Library
These days even small devices like cellphone have multiple processors. Since the last 10–15 years, there is so much improvement in processors capability, RAM, SDD, CPU power and clock time. Which helped in running applications more efficiently and fast. The focus these days has now shifted on developers to write code that can use CPU and processors more efficiently.
Thread Quantum is the amount of time that the schedule allows a thread to run before scheduling a different thread to run.
Context Switch — The scheduler maintains a queue of executable threads for each priority level. These are known as ready threads. When a processor becomes available, the system performs a context switch. The steps in a context switch are:
- Save the context of the thread that just finished executing.
- Place the thread that just finished executing at the end of the queue for its priority.
- Find the highest priority queue that contains ready threads.
- Remove the thread at the head of the queue, load its context, and execute it.
Please read more in detail, what is Thread Context Switch and Thread Quantum.
TPL — Task Parallel Library
Keeping this in mind .Net introduced TPL — Task Parallel Library in framework 4.0, which is the preferred way of achieving parallelism in your code. The TPL gives you a lot of control over how your code is run, allows you to determine what happens when errors occur, provides the ability to conditionally chain multiple methods in sequence
TPL uses the .Net thread pool, but it does so much efficiently, executing multiple tasks on a single thread sequentially before returning the thread back to pool. It saves CPU clock or thread quantum caused by small tasks and avoids the creation of new threads.
TASKS
You can create a task or parallel thread of execution using Task class. It provides a callback, that we can use to define a subsequent task to run asynchronously.
If the subsequent task is very small, you may want to execute it synchronously on the same thread to avoid context switch overhead
If the continuation task is called on an I/O thread, then you may not want to block the thread using ‘TaskContinuationOptions.ExecuteSynchronously’ option.
If you need a long-running Task, you can create it with the TaskCreationOptions.LongRunning flag with the Task.Factory.StartNew method.
You can do multiple continuations of a task that can execute in parallel
You can also chain the dependencies, the second task will run only after the first task is completed.
You can invoke a continuation only when multiple Tasks are completed (or any one of them has) using ContinueWhenAll( ) and ContinueWhenAny( ) methods.
Although it is not a common practice but you can cancel a task using CancellationToken.
How to Optimize Parallel.For/ForEach
Please do read Parallel.For and Parallel.ForEach as well. These methods are very commonly used by everyone but not efficiently.
Sometimes we have a very huge collection to process but the overall processing in an iteration is small enough to prevent us from using a parallel loop.
But I have seen developers ignoring the overhead cost of thread quantum and context switch, thus resulting in even worse performance than in a single thread.
.Net provides Concurrent.Partitioner class to handle such scenarios. Parttioner class breaks the collection in subsets and execute them in parallel threads.
In layman terms, let say we have 10 millions records in a collection, but with help of partitioner and parallel loops, we can process this collection in 10 parallel loops, with each processing 1 million records. Check following code for reference.
I only covered a very small topic of TPL and performance improvements in .Net code, but there is so much more to discover.
Please read the Ben Watson’s masterpiece “Writing High Performance .Net Code” and don’t forget to clap this article.
Cheers