Awaiting the Block
The await keyword in C# is magical. It seems to just work. Throw an await in front of a resource intensive function and slap an async on the method. Do this on all the methods in the chain and it just works. Right?
Although it does just work most of the time there are quite a few gotcha’s. There are many best practices that have been shared to alleviate some of the scenario’s you might run into.
I recently watched an awesome Pluralsight Video by Jon Skeet that covered Asynchronous code in C# 5. I recommend the video (and Pluralsight in general) as it goes into great depth on how async works under the covers, which is out of scope for this article.
One of Async’s best use cases is IO related tasks, such as calling web services. When adding async to a webservice call you will likely see benefits. If, however, you do intensive processing before or after making the web service call you could end up in a blocked state.
Blocked
Take a look at the code below from a click even on a simple WinForms app. What do you think will happen to the responsiveness of the form?
private void noAwaitClick(object sender, EventArgs e)
{
label1.Text = "starting...";
long prime = this.FindPrimeNumber(PRIME_TO_FIND);
label1.Text = prime.ToString();
}
If you guessed that the form will freeze up until the prime number is calculated then you are correct. Using the well known methods to spin off worker threads and then calling invoke on the UI thread is one way to get responsiveness back. This can be a bit tedious and difficult for beginners to understand. With async and await we have a new, simpler way to stay responsive.
Async to the Rescue
Let’s take a look at the code block below. What do you think will happen to the responsiveness of the form this time?
private async void async1Click(object sender, EventArgs e)
{
label1.Text = "starting...";
var prime= await this.primenumberAsync();
label1.Text = prime.ToString();
}
Did you guess that it would be responsive? Good guess, but this was a trick question and the gotcha I would like
to share with you. To understand why this is not going to keep the UI responsive we have see the implementation of
the primenumberAsync()
and have basic understanding of what is happening when
the code is compiled using the await and async keywords.
Let’s see what is happening in the implementation of primenumberAsync()
:
private async Task<long> primenumberAsync()
{
long prime = this.FindPrimeNumber(PRIME_TO_FIND);
return await Task.FromResult(prime);
}
We are simply calling FindPrimeNumber
and creating a Task
from the result. Now if you are like me,
you might be thinking “Doesn’t await and async create new threads when primenumberAsync()
is called?”
Under the Covers
To see why the async1Click()
code above blocks we have to have a understand of how await and async
gets compiled.
The key words await and async actually get compiled into a state machine. The compiled state machine is how the asynchronous behavior is achieved. I would
recommend watching the pluralsight video or reading an overview of c#’s asyncrounous statemachine
article to get a better understanding of the generated state machine.
The key take away here is that the await and async keywords do not create threads. Instead the compiler generates a state machine which, in this case, runs on the same UI thread.
Since the work is not being done on a separate thread and requires intensive processing power, the UI thread gets
blocked. You can see the code being executed on the main thread if you download the sample code from the github
repository. Run the code, set a break point inside of the FindPrimeNumber
routine and use the [thread viewer]
(http://msdn.microsoft.com/en-us/library/w15yf86f.aspx).
Unblocked
To create a responsive UI we can leave our call in the button handler the same but re-implement primenumberAsync()
to create a new thread:
private async Task<long> primenumberAsync2()
{
var prime = await Task.Run(() => this.FindPrimeNumber(PRIME_TO_FIND));
return prime;
}
Conclusion
Await and async are power additions to the C# language but with great power comes great responsibility. In this post we took a look at one scenario where await and async are not magic bullets and a little bit of care needs to be taken to make sure that any extra processing is done a separate thread.
Comments