Await-ing happiness

Introduction

There are currently two main patterns for asynchronous programming in .NET:

  • The ‘original’ asynchronous programming pattern: IAsyncResult, BeginInvoke, and EndInvoke
  • Event asynchronous programming (EAP): a Begin method and a Completed event

Using a Task-based asynchronous pattern has a number of advantages over the above, and in most cases will result in much easier to follow code. However, unless you’re already familiar with the concepts involved, the way it functions will almost certainly be quite different from what you’re used to and to your current mental model of asynchronous work. This means that while I would encourage you to get familiar with the patterns and practices involved, it can take some time working with Tasks to become comfortable with them and to think in their language.

Similar concepts exist in other languages / frameworks and may have more intuitive names, depending on what you believe is in a name: they are often called promises or futures. Linky.

Currently, if you want to offload some work from the main thread of an application, you’re likely to get hold of a background or threadpool thread and start it running. Now you either have no idea when it completes and so no way to return a value, or you have a callback for completion (something like BackgroundWorker). Working like this causes you to split your logic between two methods, one before starting the background work and one after it has completed; in more complex situations it can quickly become difficult to work with your code, to understand the flow of you application, and to reason about concurrency issues.

The basics

Slightly simplistically, a Task or Task represents a method that is in some state of execution, that is it is either about to execute, currently executing, or has already executed. Unlike previous ways of working with asynchronous code in .NET, this allows us to keep hold of a reference to the asynchronously executing code.

So how do you know when your Task has actually completed? You await it:

Task task = FrobnicateAsync();
await task;

This allows us to keep all of our logic in a single method, and makes the flow of our code much easier to follow. In fact the code reads and executes almost exactly the same as if there were nothing asynchronous happening at all; this is the unicorn – making code execute asynchronously without increasing complexity.

// logic before the async work
await FrobnicateAsync();
// logic after the async work completes

Rules to live by

I’m mostly thinking of the WPF world here, but much of the same applies no matter where you are.

Don’t think of async/await as a silver bullet
Like anything, it has it’s quirks and pitfalls but its advantages hugely outweigh these in almost all cases.

Never use async void
With async void you have no Task and so lose your context / reference to the asynchronously executing code and have no way to know when it completes; this puts us back into the ‘old-world’ of asynchronous programming.

… except when the context is already out of your hands
For example void Main() and WPF UI event handlers (`void Button_Click()’ &c) where it was not your code that invoked them in the first place.

Don’t be impatient, always await
Again, if you don’t await a Task, you’ve lost your context to the asynchronously executing code and we’ve gained nothing from using Tasks.

Never call .Wait() or .Result
This is more applicable to WPF as doing so is likely to cause a deadlock when done on the UI thread, but actually applies more widely. While these methods exist for good reason, calling them should be extremely rare, as this breaks the next rule…

Don’t mix and match
It is a seriously bad idea to just throw Tasks into the middle of an existing application hoping it will solve all of your problems, it’s more likely to cause unexpected behaviour than it is to actually solve anything. It’s often significantly simpler to introduce async ‘top-down’; that is, making your entry-points async (Button_Click &c) and spreading async down into your application from there. Other approaches inevitably lead to either async void or calls to .Result and the problems mentioned above.

await GetConclusionAsync();

There are a number of ‘work-arounds’ for various problems that people have had with TPL to be found, however I find that more often than not these are solutions not to problems in the framework but to problems caused by the misuse of it. Now, I’m not saying that there are no hard parts, but if you take the time to grok the TPL and get a feel for it, then you’ll encounter far fewer ‘problems’ with it.

Put in the time and respect it deserves and I promise, you’ll love it.