Plainionist Tales of a software craftsman's journey

What actually is Task<T>?

If you see such source code, do you immediately think of multi-threading?

What if I would tell you that Task<T> is nothing but a fancy delegate which is not coupled to threads at all?

Let’s start with some very simple component. It provides a single API which accepts a request object, does some computation and returns a response object.

Such an API design obviously forces the caller to “actively” wait for the response to be available in order to be able to continue the processing. This design couples the control flow of processing a request and the control flow of handling its response.

Now let’s assume you want to decouple these two parts of the control flow e.g. because the processing of the request is delayed because of some queue or it runs in a different thread or process or even on a different machine or in the cloud.

One common way to achieve this in .NET is using events.

(Hint: This sample code is kept as simple as possible, event deregistration is skipped for this reason.)

An alternative to .NET events would be passing a simple delegate which we could also call continuation.

In order to improve the readability of this design, let’s get it closer to the initial request-response semantic by inventing a small class called Promise which is returned from the Execute API and which is then used to hook up the continuation.

Let’s also add error handling to this design.

The implementation of the IComponent interface would use the Promise class like this:

Now this design is already pretty close to the one of Task<T> which shows us that Task<T> is actually nothing but an implementation of a promise provided by .NET.

But what about async/await? Isn’t that what makes Task<T> so powerful? Well, actually async/await is an compiler feature which is completely independent from Task<T> and the Task Parallel Library (TPL). To prove this, let’s enable it for our custom Promise implementation as well.

Therefore, we just need to provide an API (e.g. an extension method) called GetAwaiter which returns a type or an interface which has the following properties:

  • it implements INotifyCompletion
  • it provides an API called IsCompleted
  • it provides an API called GetResult

And with this the compiler allows us to use async/await for the Execute API as usual.

In essence, that’s exactly how Task<T> works! So let’s use it instead of our custom Promise implementation.

Quod erat demonstrandum.

Task<T> and async/await are quite convenient and powerful concepts when dealing with asynchronous APIs and concurrency BUT neither of these concepts is causing (!) threads or concurrency.

Full source code:

Tags: design