In part 1 of this series we looked at the async
keyword. async
without await
is possible (though not very useful) but await
without async
not. If you want to use await
inside a method it must be marked as async
.
The await keyword
Here is an example of a method using await
:
Apart from the keywords async
and await
and the funny return value this looks just as you would write a synchronous method: Download a web page, then apply a regular expression to its contents to see if it matches.
And that is just the point. Although this method is asynchronous, it looks and feels like a synchronous method. But unlike its counterpart, the async
method won’t block the caller until it is finished, won’t stay stuck in your mouse click event handler and, most importantly, won’t freeze the rest of your application.
How does does it work?
What happens when this method is called is that it executes just like any other method up to the point where the first await
is encountered. At that point, the method returns to the caller with a Task
(let’s call it method task) that contains information about the state of its work. As soon as the Task
returned by GetStringAsync()
completes the remainder of the method is executed. Note that all local variables are preserved and are available just as in a standard non-async
method. When the method is finished with completing the remainder of its work (applying the regular expression) it will magically set the method task’s Result
property to the value passed to the return
statement and mark the Task
as completed.
Typically, calls to async
methods will be called and awaited by other async
methods so the caller of our DoesWebContentMatchPatternAsync()
method will not have to do anything special to receive this result. All it takes is writing await
in front of the method call.
This will generate a compiler error if that method is not async
.
How do I introduce await?
In practice, you’ll see this happen a lot when asyncifying existing code. The procedure is always the same:
- Add an
await
inside a method. - Realize it doesn’t compile anymore because it is not
async
. - Add
async
to the method’s declaration. -
- If the return type was
void
, change it toTask
. - If the return type was of any type
T
, change it toTask<T>
.
- If the return type was
- Add the
Async
suffix to your method name using your favorite refactoring tool. - Repeat from step 1 for the calling method.
This typically leads to async
/await
creeping through your entire codebase after some time but will leave a good feeling of accomplishment afterwards.
But what about exceptions?
But the magic of await
doesn’t stop there. Another tedious task of asynchronous programming is catching and handling errors that occur in code running in the background. async
/await
handles this problem very elegantly by propagating exceptions through the Task
‘s IsFaulted
and Exception
properties. What this means is that you can write exception handling code just as you would with synchronous code.
If you don’t catch the exception it will propagate to the calling method, just like with synchronous code. As long a your code is async
/await
all the way you won’t have to do anything differently.
Background work and the UI thread
The last piece of magic comes in the form of context synchronization. Another common pitfall in asynchronous programming comes from the fact that most UI technologies rely on a single UI thread to handle all modifications of the UI. Modifying a UI element from a different thread will typically lead to an exception being thrown. Let’s look at another piece of code using our method from above:
The code shows an event handler for a button click in a WPF code-behind (so not the place you would typically implement this functionality). The line assigning a new value to resultBox.Text
is a line that has to be called from the UI thread in WPF. However, there is no code marshaling the call back to the UI context. This is because the default behavior of async
/await
is to try and execute the remainder of the code following an await
call back into the original context for you.
This behavior is typically only needed for UI code and should be disabled for all other code as it adds overhead and may cause deadlocks. Disabling is achieved by calling ConfigureAwait(false)
on the Task
to be awaited:
This tells the compiler that it is OK to execute the remainder of the method (i.e., everything following the call to await
) in the same context as the work done in the awaited call.
Give it a shot!
If you’re already on .NET 4.5 I suggest you give async
/await
a try. It will change the way you look at asynchronous programming.