When Microsoft released .NET Framework 4.0 in April 2010, the Task Parallel Library (TPL) was introduced to help developers replace the previously used Asynchronous Programming Model (APM) pattern for a Task-based asynchronous programming.
Before the introduction of Tasks, when implementing asynchronous code, developers had to define two variations of the same method: one to begin the operation execution (convention: BeginOperationName
), that would receive an optional callback to be invoked when completed, and another to wait for the operation to complete (convention: EndOperationName
) and get the result or an exception, usually used inside the callback to prevent the main thread to be blocked.
As an example, imagine a repository of cars with an asynchronous method for getting one by a given plate number, implemented using the APM pattern:
1 | public interface ICarRepository |
As you can see, creating two methods per operation is one of the most obvious and annoying disadvantages of the APM pattern, while the other is the need to implement your own wrapper for IAsyncResult
so you can trigger the WaitHandle
and invoke the callbacks when the operation completes.
Another disadvantage was the cancellation of running operations. If it was supported — and that’s a big if — there wasn’t a standardized pattern for developers to follow. Some would create another method for canceling (i.e. CancelOperationName
) that receives an IAsyncResult
, others would provide a method directly into the IAsyncResult
and either return their own interface or require a cast, and others would simply ignore this feature due to sheer complexity.
Because Microsoft knew asynchronous programming was very important for the future of .NET, it decided to solve these problems by introducing the Task Parallel Library, making it easier for developers to add parallelism and concurrency to applications.
The Task Parallel Library has two central pieces:
- The
CancellationToken
is a structure commonly used in asynchronous methods and enables developers to register a callback that will be invoked if a cancellation is requested. This provides a standardized approach for implementing asynchronous operations that can be canceled mid execution by simply receiving aCancellationToken
as a method parameter. - The
Task
andTask<T>
are classes that merge bothIAsyncResult
andAsyncCallback
concepts. Developers can return a Task and the caller would either register a callback withContinueWith
, for a non-blocking approach, or use the methodWait/Result
and block the main thread until theTask
was completed. This removes the need to have callback parameters and only a single method is needed instead ofBeginX/EndX
methods.
If we change the previous car repository example from APM pattern to use TPL, it would be much simpler:
1 | public interface ICarRepository |
Now that we have an idea about Task
, Task<T>
, CancellationToken
and some of the reasons why Microsoft created the Task Parallel Library, let’s analyze two other important classes that aren’t commonly used but make all of this possible — TaskCompletionSource
and CancellationTokenSource
.
TaskCompletionSource
The class TaskCompletionSource
is used to create a Task
and provides methods to mark it as completed in one from three possible states:
- RanToCompletion — the methods
SetResult
orTrySetResult
complete the task successfully and, in case of aTask<T>
, the result can be retrieved; - Canceled — the methods
SetCanceled
orTrySetCanceled
mark the task as cancelled mid-execution. Waiting or retrieving the result will throw aTaskCanceledException
; - Faulted — the methods
SetException
orTrySetException
mark the task as faulted and waiting or retrieving the result will throw the exception.
With this class developers can easily implement Task-based asynchronous programming. On later versions of the .NET Framework it was widely used to convert classes implementing the APM pattern and, with an increase adoption of tasks, developers also started to migrate their own libs.
Let’s imagine the car repository was still implemented using the APM pattern and we wanted to create some extension methods for developers that prefer to use tasks.
1 | public interface ICarRepository |
As you can see, we just converted BeginX/EndX
methods to tasks in just a few lines without blocking the caller thread.
The TaskCompletionSource
is also perfect to convert Event-based Asynchronous Pattern (EAP). Imagine you have a class that represents a message queue and it provides two events — OnMessageReceived
and OnErrorReceived
— and a non-blocking method to send a message.
1 | public interface IMessageQueue |
Let’s say we wanted to create an extension method that sends a message and, without blocking the current thread, waits for a correlated response or exception to be received.
1 | public static class MessageQueueExtensions |
The approach is very similar to the APM pattern. We do a temporary registration into the relevant events, then invoke the action that can trigger those events and change the task state when callbacks are invoked. Just be sure registrations are properly cleaned up or else a memory leak will happen.
CancellationTokenSource
The class CancellationTokenSource
is used to create a CancellationToken
that can be manually marked as canceled using the method Cancel
or, to support timeout implementations, the CancelAfter
methods that will schedule a cancellation after the specified time as passed.
Using the previously defined GetByPlateNumberAsync
extension method, let’s change it first to support cancellation using a CancellationToken
. Please note that some performance improvements could be made when detecting for cancellation, but this implementation is for simplicity:
1 | public static class CarRepositoryExtensions |
Now that GetByPlateNumberAsync
supports cancellation, if we wanted to limit the waiting for a response to a maximum of 5 seconds, a CancellationTokenSource
could be used:
1 | using var cts = new CancellationTokenSource(); |
The CancellationTokenSource
also supports to be linked to an existing CancellationToken
meaning that the underlying token can either be cancelled manually or by the linked token. This is usually useful to implement methods that offer some kind of timeout parameter without the caller having to use a CancellationTokenSource
directly.
Let’s change the previously defined SendAsync
extension method to support both cancellation using a token or a timeout parameter that will throw a TimeoutException
if the waiting time is exceeded:
1 | public static class MessageQueueExtensions |
In this case, we now use the method CreateLinkedTokenSource
to ensure the CancellationTokenSource
token will be cancelled if the ct
parameter is cancelled or if a given timeout has passed. Because we are listening for the linked cts
token, inside the callback we must check if the ct
parameter was the cause of cancellation so we can either mark the task as canceled or faulted with a TimeoutException
.
Conclusion
In this article I explained some of the reasons why Microsoft decided to replace the Asynchronous Programming Model (APM) pattern for the Task-based Asynchronous Programming, which is possible by using classes from the Task Parallel Library (TPL).
I also demonstrated how the classes TaskCompletionSource
and CancellationTokenSource
could be used to convert any asynchronous implementation to tasks, even if based on Event-based Asynchronous Pattern (EAP), ensuring threads won’t be blocked waiting for responses and even supporting the cancellation of running operations.