pashov.net/code|February 14, 2016

Daily Archives: February 14, 2016

.NET

Async/Await done right in the context of a web app

Published by:

Since .NET 4.5 we are provided with a handy way for async programming through the await-async operators. However, i feel that these are widely misunderstood as looking at peer’s code.

First things first – what are async-await good for in a web app?

Making your IO bound or computational intensive methods async, makes your app much more scalable. It releases the thread to do any other work (most often serving requests) instead of waiting for computations and IO read/write. If your server can handle 100 people synchronously, it should be ok with at least double that if you use async methods (rule of thumb – depends on many things).

What is a bad idea to use async-await?

If i make a request and that triggers a very slow job, i will be waiting a heck lot of a time until the job is done and the response returned no matter if i use synchronous or asynchronous methods. The benefit of async methods is not that they return fast (you have to await them at the end of the day) but the fact that they utilize threads until doing slow jobs. So response time should be the same but in the mean time the released thread could have server another request before continuing the first job after the await. So if is about utilizing resources and not returning immediately. If you want to return a result to the client immediately and do a job in the background that is a whole new topic. For example, when you make a request to open stack to provision a virtual machine, you get 201 Created immediately, no matter that it takes 10 minutes to provision it. So that is definitely not async-await pattern but rather background workers, queues etc.

Good practice

If you decide to implement async methods go all the way – don’t mix up synchronous and asynchronous code. This may cause deadlock very often. The code below is a deadlock:

[HttpGet]
[Route("api/files")]
public IHttpActionResult Files()
{
    var resultFromLongComputation = this.TestAsync().Result;
    return Ok(resultFromLongComputation);
}

public async Task<int> TestAsync()
{
    await Task.Delay(2000);
    return 42;
}

So we have an async method where we await a long job. When hitting the await line we return immediately in the Files method where we synchronously start to wait for the result from the TestAsync method (the .Result forces the waiting for a Task to complete in synchronous mode). The thread and the related context hang. When the Delay elapses, the TestAsync method wants to get hold of the previous bound context so that it finishes its work and returns a result. However, that context is being blocked by the Files method (it is in a synchronous wait).

How to prevent it?
  1. One way would be to use
public IHttpActionResult Files()
{
    var resultFromLongComputation = await this.TestAsync();
    return Ok(resultFromLongComputation);
}
  2. Another way is to set the ConfigureAwait to false which prevents the method for waiting for the same context to be available. It will grab a thread pool thread instead.
public async Task TestAsync()
{
    await Task.Delay(2000).ConfigureAwait(false);
    return 42;
}

The deadlock is valid for UI apps also (Win forms and WPF) but not for console apps (async in a console app is not bound to the request context or the ui context but just grabs a thread pool thread). So if you have a testing console that is very confusing.

Some words on the Synchronization Context

The default behavior of awaiting a task is to capture the current Synchronization Context and when the task is finished to try to post the invocation of the continuation delegate (compiler generated code that wraps the stuff after the await) on the same Synchronization Context. Synchronization Context will be null in a console app and will be not null in a UI or Web App. So that’s why all the hassle with the contexts and mixing up synchronous and asynchronous code.

Further Reading:

Stephen Cleary intro to async-await

Async/Await FAQ in the pfxteam team msdn blog