C#’s async, await, and .Result

In Market Invoice, there are many places where async and await are used. Recently, I introduced a bug that an operation gets deadlocked by replacing await with .Result. I was bitten hard 🙂

Non-blocking execution

When using async and await, C# run time generates a state machine in the background

public async Task CallingMethodAsync()
{
    Task<int> longRunningTask = LongRunningOperationAsync(); // 1)
    // independent work which doesn't need the result of LongRunningOperationAsync can be done here

    //and now we call await on the task
    int result = await longRunningTask; // 2)

    //use the result
    Console.WriteLine(result);
}

public async Task<int> LongRunningOperationAsync()
{
    await Task.Delay(1000); //1 seconds delay
    return 1;
}

1) LongRunningOperationAsync is running. But it doesn’t block the execution of CallingMethodAsync, until the execution point reaches 2)

Now the execution point reached 2). If LongRunningOperationAsync() is fully done, the result will be ready, and it will be assigned to result straight away. However, if LongRunningOperationAsync() is still running, the execution of CallingMethodAsync will stop there, waiting until LongRunningOperationAsync() finishes. Once it finishes, CallingMethodAsync will resume the execution.

Call-back without its hell

Let’s look at Eric’s Serve Breakfast example.

void ServeBreakfast(Customer diner)
{
    var order = ObtainOrder(diner);
    var ingredients = ObtainIngredients(order);
    var recipe = ObtainRecipe(order);
    var meal = recipe.Prepare(ingredients);
    diner.Give(meal);
}

In this example, every customer must wait until the previous customer’s breakfast is fully prepared and served. You can see people would get angry very soon.

In order to receive orders while preparing for breakfast, you have to take orders in an asynchronous manner. It will bring it javascript’ call-back hell.

void ServeBreakfast(Diner diner)
{
  ObtainOrderAsync(diner, order =>
  {
    ObtainIngredientsAsync(order, ingredients =>
    {
      ObtainRecipeAsync(order, recipe =>
      {
        recipe.PrepareAsync(ingredients, meal =>
        {
          diner.Give(meal);
        })})})});
}

The code is not very readable. Computers may like it, but humans are not good at following up the callbacks.

This can be rewritten in the new style, reads much more nicely.

async void ServeBreakfast(Diner diner)
{
  var order = await ObtainOrderAsync(diner);
  var ingredients = await ObtainIngredientsAsync(order);
  var recipe = await ObtainRecipeAsync(order);
  var meal = await recipe.PrepareAsync(ingredients);
  diner.Give(meal);
}

Now, the methods, ObtainOrderAsync() doesn’t return order. It returns Task<Order>. It’s a callback pointer. When the execution finishes, it return the result, and order is passed into ObtainIngredientsAsync()

await or .Result

Stephen Cleary recommends using await over Result.

First, await doesn’t wrap the exception in an AggregateException, which represents one or more errors that occur during application execution. So, you will see the real exception, not the bland AggregateException. .Result wrap an exceptions that happens in the async method into AggregateException.

try {
    details = await _service.GetDetails(personId);
    ...
} catch (ApplicationException) { // this catch will work, as await pass the exception as it is.
    ...
}

For .Result, you have to catch AggregateException.

Second, Result / Wait can cause deadlocks. The async method will continue to run, and the task will be returned to it. When the task comes back, and if it’s not completed yet, it will hang in the current context.

public class CompanyDetailsController : ApiController
{
    public string Get()
    {
       var task = GetCompanyDetails(...);
       return task.Result.ToString(); // if task hasn't been completed, this will block the thread.
    }
}

public static async Task<CompanyDetails> GetCompanyDetails(Uri uri)
{
    using (var client = new HttpClient())
    {
        var jsonString = await client.GetStringAsync(uri);
        return CompanyDetails.Parse(jsonString);
    }
}

Preventing the deadlock

ConfigureAwait

await Task.Delay(1000).ConfigureAwait(
    continueOnCapturedContext: false);
  // Code here runs without the original context. (if the original context is UI thread, then UI thread context)

By using ConfigureAwait, you enable parallelism that the asynchrounous code can run in parallel with the thread the original context is in. As a result, you can avoid the deadlock

avoid Result / Wait

As Result causes deadlocks, don’t use it. Instead, favour await and use async on the method all they down or up.

 

Resources

 

C#’s async, await, and .Result

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s