Async & await

In this post we are going to explore the keywords that came with TPL in .Net 4.5 and were introduced to take the asynchronous programming experience as close to the synchronous as possible. We will look at this programming model, how it enables the creation of responsive UIs and how it can also be useful for other purposes.

What this post will cover

  • What do the async and await keywords do
  • How to use them

What this post will not cover

  • What the TPL is
  • What a Tasks is
  • How to handle mutable shared state

The async keyword does not make your code run asynchronously, yet it enables the use of await. The await keyword in turn takes your code to run asynchronously. We will take a look at how this is achieved. The async keyword is just necessary for backwards compatibility. This is because introducing a new keyword is not easy in a mature language like C#. (what if somewhere a parameter/local variable is named await that code would then be broken with the introduction of the await keyword).

To find out what await essentially does, we look at the following example (assume we are in a UI applications code behind with a textBox Control):

public void SomeTextBoxUpdate()
{
int someCalculatedResult = TaskFactory.StartNew<int>(() => {...});
task.ContinueWith(adjacent => 
{
this.textBox.Text = someCalculatedResult; 
});
}

instead of this code which would block the UI:

public void SomeTextBoxUpdate()
{
int someCalculatedResult = ...;
this.textBox.Text = someCalculatedResult; 
}

The compiler does a little more, but in principle this is the idea of the basic mechanics of await.

But the above code is not very similar to synchronous code (also the continuation would need to dispatch to the UI thread, which we ignored for simplicities sake), that is why the same code with async/await looks way more familiar (except for those keywords):

public async void SomeTextBoxUpdate()
{
int someCalculatedResult = await TaskFactory.StartNew<int>(() => {...});
this.textBox.Text = someCalculatedResult; 
}

Yet the registration of a continuation is not all the await keyword does. It also does the following things for the developer to further ease the asynchronous programming model:

  • Registers continuation (as we saw)
  • Does not perform a physical wait (await SomethingAsync() != SomethingAsync().Result), instead it gives up the current thread and returns the control to the caller (as was expected with a continuation)
  • preserves the structure of synchronous code
  • Synchronizes back to UI Thread when it is done!! (very helpful for UI applications, no Dispatcher etc.). This is done by using the current SynchronizationContext.Current when await is called.
  • If SynchronizationContext.Current == null when the await is called, the current task scheduler is used to continue continuation on a thread pool thread
  • Coerces the result of the continuation, even exceptions!

The use of async/await is also very effective for non UI features:

  • Continuations reduce burden on the thread pool
  • Convenient programming (similar to synchronous model)

Prerequisites for the use of async/await

To use async/ await keywords, some requirements need to be met. First of all you obviously need to use .Net 4.5 or above (.Net 4.0 is also sufficient if you get the Microsoft.Bcl.Async Nuget package). Your Visual Studio needs to be at least of version 2012.

Also the method signature needs the async keyword, and must have a return value of the following types:

  • void
  • Task
  • Task<T>

Also a method that uses the async keyword in its signature, must at least have one use of await in the method body.

The void return type is there for fire and forget style methods. This can be applied in several scenarios, but makes the most sense for Event handlers or such (like Commands) in UI applications. (this leads to the issue that the async method signature with a Task needs to be employed by all methods down to the lowest level, which can lead to restructuring a lot of your code. So to use async/await effectively it is best to do so from the design/architectural time of your application).

You need to return Task<T> or Task, because this instructs the caller that the called method runs asynchronous. This is more explicit on the one hand but more importantly allows for error handling as close to the error as possible, because as mentioned above the await keyword coerces the result, even though it might be an exception. The next thing is, that it enables the compiler to return early and thus give control back to the current thread.

A fire and forget style method with void as return type rethrows the exception on the SynchronizationContext.Current that was present at call to the async void method. If not handled there, it bubbles up . This means logging on Application.HandleUnObservedTask might be the only “handling” that is appropriate.

The best thing of async/await is that the method, when awaited, still returns only <T> or void. This is taken care of by the compiler. So the compiler takes care for you of the Task handling. If any Exception is thrown during a call to an async method that returns task, this Task then has a completed state of faulted and can be handled by the usual techniques (see Intro to Tasks).

The requirement to return a Task essentially ensures the following two things:

  1. It makes the caller aware of the asynchronicity of the method
  2. It ensures the caller is always able to observe the outcome (be it a result or an exception)

Should you always continue on SynchronizationContext ?

Or in other words is the default behavior always appropriate?
For this thought, consider the following snippet:

string result = await OneThingAsync();
string text = await Task.Run<string>(() => AnotherThing(result));
this.textbox.Text = text;

We did this, because think of the AnotherThing method as another long running operation, that else would block the SynchronizationContext. (which we want to avoid with the use of await in the first place).

Because await registers a continuation that synchronizes on the current context (lets assume this is the UI thread for simplicities sake), between those above two methods, for the briefest moment the result is marshaled to the UI thread only to give up control immediately thereafter. This is a waste of resources.

This is why one should try to use a ContinueWith for this style of usage of await:

await OneThingAsync().ContinueWith(adjacent => AnotherThing(adjacent.Result));

But this does not resemble synchronous code, so we need to find a better readable solution. Which is provided by the following:

string result = await OneThingAsync().ConfigureAwait(false);
AnotherThing(result);

What this does is, that it does not marshall back to SyncContext, like the default behavior of await suggests.
AnotherThing is now running on a thread pool thread, you essentially removed the continuation from await.

If in the wake of returning from this method the UI should be udpdated you have two options:

  1. Create a new method that returns Task and encapsulate those two method calls. Then await this new method in the default way (ConfigureAwait(true) so to speak)
  2. Employ the use Dispatcher or SynchronizationContext.Current yourself

Task helper methods

With .Net 4.5 there came several Task class methods to aid the development with await keyword. We will now briefly look into them to complete the view on async/await with Tasks. Those we are looking at in this post are:

  • Task.Delay
  • Task.WhenAll
  • Task.WhenAny

Task.Delay

When you use Thread.Sleep there is no CPU time used, yet memory resources are still consumed. To avoid this (because it might probably easy for desktop, but maybe not on server side) you can use the Task.Delay method. This gives up the thread and therefore frees those resources. Also it gives you the necessary Task to await and deem the method asynchronous. So essentially it represents an efficient way of waiting on completion.

Task.WhenAll & Error Handling

The Task.WhenAll method gets a collection of Tasks and in turn creates a Task that completes if all the supplied tasks have completed. This allows you to await a set of tasks to complete before proceeding. This also, because it returns an awaitable Task, does not block the calling thread.

So with Task.WhenAll you could instead of this, which does not block the UI either:

public async Task ProcessAsync()
{
for (i = 0; i < 10; i++)
{ 
string result = await LoadAsync();
UpdateUi(result);
}
}

use this for better performance, even though the structure is more obtrusive:

public async Task ProcessAsync()
{
var list = new List<Task<string>>();
for (i = 0; i < 10; i++)
{ 
list.Add(LoadAsync());
}
await Task.WhenAll(list);
list.ForEach(t => UpdateUi(t.Result));
}

This now allows for the Tasks to run in parallel, instead of sequentially.

Error handling

As always, do not refrain from handling errors as close to its cause as possible. Do this with Task.WhenAll, add to above method:

...
Task allTasks = await Task.WhenAll(list);
try
{
await allTasks;
list.ForEach(t => UpdateUi(t.Result));
}
catch (AggregateException e)
{
allTasks.Exception.Handle(e => 
{
Log(e);
return true;
}
}

 

Task.WhenAny

Task.WhenAny also takes a collection of tasks to be run and then returns an awaitable Task. Yet in comparison to Task.WhenAll it completes if any of the supplied tasks has ran to completion. This can be used to query different services and get the quickest answer possible.

Summary

In this post we looked at the async and await keywords from TPL that were introduced with .Net 4.5. We looked at how the syntax of them works and how these keywords can be used to create asynchronous code that pretty much matches synchronous coding style.

We also saw what the await keyword does for you and how you can control this behaviors.
Last but not least we looked at the other Task methods that complete the working with Tasks in an easy and more synchronous manner.

Categories: TPL

0 Comments

Leave a Reply

Avatar placeholder