Blocking Asynchronous Code

Following on from a previous article I wrote as an introduction to writing asynchronous code with .NET, I want to describe a common problem I see developers making when they begin writing asynchronous code beyond the basics. A common mistake developers make when they first start writing asynchronous code using the .NET Framework, is to write blocking asynchronouscode. I’ve seen this problem on Stackoverflow and with developers I have worked with directly (both junior and senior).

Rather than try to explain the problem, I’ll give some example code that should hopefully highlight the problem. Here’s an ASP.NET Web API RESTful service being invoked from a client application.

The back-end service code is taken from one of our RESTful services that returns vehicle telemetry to a client application. For the purposes of clarity, I have omitted all logging, error checking and authentication code.

Hide Copy Code

And here is a client that invokes the RESTful service. In this example the client is a unit test.

Hide Copy Code

The above unit test code will deadlock. Remember, that after you await a Task, when the method continues it will continue in a context.

1. The unit test calls the Get() RESTful service (within the ASP.NET Web API context).
2. The Get() method in turn calls the GetData() method.
3. The GetData() method returns an incomplete Task indicating that the Get() method has not yet completed (with the same context).
4. The Get() method awaits the Task returned by the GetData() method (the context is saved and can be re-instated later).
5. The unit test synchronously blocks on the Task returned by the Get() method which in turn blocks the context thread.
6. Eventually the Get() method will complete. This in turn completes the Task that was returned by the GetData() method.
7. The continuation for Get() is now ready to run, and it waits for the context to be available to allow it to execute in the context.
8. Deadlock. The unit test is blocking the context thread, waiting for the Get() method to complete, and GetData() is waiting for the context to be available so it can complete.

How can this situation be prevented? Simple. Don’t block on Tasks.

1. Use async all the way down
2. Make (careful) use of ConfigureAwait(false)

For the first suggestion, awaitable code should always be executed asynchronously. So given the example code here, the unit test was not correctly awaiting the result from the RESTful service. The unit test code should be modified as follows.

Hide Copy Code

Like a handshake, whenever you have an await at one end of a service call, you should have async at the other.

The use of ConfigureAwait(false) is slightly more complicated. When an incomplete Task is awaited, the current context is captured to allow the method to be resumed when the task eventually completes e.g. after the await keyword. The context is null if invoked from a thread that is NOT the UI thread. Otherwise it returns the UI specific context depending on the specific platform you are using e.g. ASP.NET, WinForm etc). It is this constant context switching between UI thread context and worker thread context that can cause performance issues. These issues may lead to a less responsive application, especially as the amount of async code grows (due to the increased volume of context-switching). Yet this is exactly what we are trying to solve by using asynchronouscode in the first place.

There are a few rules to bear in mind when using ConfigureAwait(false)

- The UI should always be updated on the UI thread i.e. you should not use ConfigureAwait(false) when the code immediately after the await updates the UI
- Each async method has its own context which means that the calling methods are not affected by ConfigureAwait()
- ConfigureAwait can return back on the original thread if the awaited task completes immediately or is already completed.

A good rule of thumb would be to separate out the context-dependent code from the context-free code. The goal is to reduce the amount of context-dependent code (which can typically include event handlers).

We can modify the Get() RESTful service as follows.

Hide Copy Code

Deadlocks such as this arise from not fully understanding asynchronous code, and the developer ends up with code that is partly synchronous and partly asynchronous.

By following the suggestions in this article, you should see performance gains in your own code, as well as better understanding how asynchronous code works under the hood.

A father, cyclist, vegetarian, atheist, geek and multiple award winning technical author. Loves real ale, fine wine and good music. All round decent chap.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store