Demystifying Async/Await
Introduction
Back in 2012, Microsoft released C# 5 with a handful of new features, one being ‘async/await’. At the company I work for, we bypassed this new feature completely, and judging by what I’ve read online, we’re not alone! However, I’m finding that more and more projects I see on GitHub are using it, and there’s generally a lot of buzz around it, and it’s widely regarded as one of the best recent features in C#.
My issue has always come to not understanding how it will benefit me. I’m very familiar with the Task Parallel Library (which simplifies threading greatly), so I just didn’t see the point. So I gave myself the task (no pun intended) of finding out what all the fuss is about, and what it offers over the TPL.
I’m not claiming to be an expert in async/await in this article – I’m just trying to give a very high level overview to allow others in my situation to grasp the subject. If anything in this article is wrong or you have any comments,. please don’t hesitate to comment below
Blocking the UI
The inexperienced, or perhaps naive, developer will likely perform this in the simplest way, which is in the UI thread.
private void startButton_Click(object sender, EventArgs e) { PerformBlocking(); } private void PerformBlocking() { textBox.Text = "Starting"; Thread.Sleep(WAIT_TIME); //2 seconds textBox.Text = "Finished"; }
This works in a vague sense of the word – but the whole application freezes, and the text box never actually shows ‘Starting’ – because by the time the UI is unfrozen, the text is ‘Finished’
This is not ideal to say the least, and if the process takes more than just 2 seconds then you’ll probably end up annoying a lot of users.
Enter multithreading
The obvious answer to this is to have the long operation execute in a separate thread. So your initial attempt might be as below
textBox.Text = "Starting"; Task.Factory.StartNew(() => { Thread.Sleep(WAIT_TIME); //2 seconds textBox.Text = "Finished"; });
I’m sure a lot of you would have guessed that this wouldn’t work – you cannot update UI controls via another thread, so setting the text to Finished will throw an exception. Some will be tempted to move this line to outside of the StartNew() call, but this will set it to ‘Finished’ straight away – as we’re not waiting for the operation to finish. You could ‘Wait’ on the task, but this will lock the UI thread again while it waits.
What I’ve always done is something like the below
private void PerformTask() { textBox.Text = "Starting"; var task = Task.Factory.StartNew(() => Thread.Sleep(WAIT_TIME)); task.ContinueWith(_ => { textBox.Text = "Finished"; }, TaskScheduler.FromCurrentSynchronizationContext()); }
Bingo! This works a treat. The ‘ContinueWith’ method takes an action that fires when the task is finished. By default, it still fires on the task thread, but the FromCurrentSynchronizationContext() call forces it to run on the UI thread.
So the end result of this test program looks like this when using this new method
Enter Async/Await
While that all works nicely, it’s a bit cumbersome to write and also to read. This is where async and await come in!
What does it do?
This is the question that always perplexed me. I couldn’t work out if it was some layer over tasks, or if it was doing something completely different.
And then I realised something which almost a ‘lightbulb’ moment for me. It is essentially similar to calling Wait on a task in that the method will continue once the task is finished, with a crucial difference.
Wait will cause the current thread to ‘block’ until the task finishes, using await will allow the thread to continue with any other executions while waiting
So what it’s actually doing is rather clever – your method in essence becomes a state machine. This allows the thread to enter your method at different stages – the entire thread isn’t forced to wait for the method to finish, it’ll simply jump back in once the awaited task is finished. You’re not necessarily creating a new thread, you’re just telling the runtime that the thread is free to carry out other tasks while waiting for your awaited task to finish.
Writing an async method
Now we get to actually writing async code, and it’s actually really simple. The code in my sample app can be rewritten as below
private async Task PerformAsync() { textBox.Text = "Starting"; await Task.Delay(WAIT_TIME); //2 seconds textBox.Text = "Finished"; }
And that is that! The result (visually at least) is identical to when running with a separate task, but it’s much more concise and functionally is quite different.
The 2 keywords above that are of interest are of course, async and await. Async is required in the method signature to use the await functionality, and it’s the await functionality that is the most interesting. You can all await on any method that returns a Task or Task<T>, and the runtime will automatically know that it can continue with other operations while it waits for your task to finish.
One difference in the above which may not be quite as obvious, is that we are using Task.Delay instead of Thread.Sleep. This is for two reasons; firstly because Thread.Sleep doesn’t return a Task (and thus is not awaitable, but also because sleeping the thread is not what we want here – the thread needs to be able to carry on wither other tasks, so it’s not the thread we want to sleep, but the actual task running on the thread.
Async all the way?
So we now have our lovely, fancy async method – how do we call it? Well, you may remember that we were triggering it via a button Click event, which is absolutely fine. However I now get the below warning in Visual Studio:
As this is a warning, this doesn’t actually stop my async method working. It’s just warning us that the the method will not wait for the PerformAsync. So if I placed more code below my call to PerformAsync, it’d execute before the PerformAsync method finishes. As you can imagine, this can cause some issues! If I was setting the textbox to something else, it’d set it before the PerformAsync method could set it to Finished.
As PerformAsync returns a Task, we could wait on this task to finish using the below code. Should work, right?
private void startButton_Click(object sender, EventArgs e) { PerformAsync().Wait(); MessageBox.Show("Finished!"); }
Wrong. The entire application locks up – it doesn’t even throw an exception. What we have here is a deadlock – we have 2 bits of code waiting for each other to run! This perplexed me, as I couldn’t grasp the point of async. Surely at some point, you need a synchronous bit of code that waits for everything to finish? I’ve read a lot about the option of using ‘ConfigureAwait(false)’ when you use the await keyword. This essentially means that any code in the async method after the await can be run on a separate thread, which resolves the deadlock. However, this doesn’t work because the code after our await accesses some UI controls – accessing these from a separate thread is blocked.
While there are a few ways you could solve the above issue, the real answer is that you can make all of your application async! As of C# 7, your Main method can be made async . But crucially in this case, we can made event handlers async. This means our ‘Click’ code can change to
private async void startButton_Click(object sender, EventArgs e) { await PerformAsync(); MessageBox.Show("Finished!"); }
And then we have truly made (this part) of the application async, without any blocking code.
Conclusion
I hope this helps daunted developers like me finally understand the point and usage of async/await. I’d go as far to say that I won’t be writing many synchronous event handlers for UI elements any more – the difference to code is fairly minimal, but the difference to the users’ experience can be extraordinary!
Thanks for the article, async/await is indeed one of the things my company completely went over.
I know this is just a “101 async/await” article of sorts, but maybe it would be advised to point out that the use of “async void Method()” is strongly not recommended, since it’s “fire and forget”, and so you can’t have a callback on it.
Except, of course, in places that already work as fire and forget, like a button’s click 🙂
Nice article, wondering which Visual Studio color Theme/configuration you are using in the examples?
Thanks, the theme I use is the just the regular ‘Dark’ theme in Visual Studio. Although all of the code snippets are not screenshots, but a WordPress syntax highlighter
Great writeup
Nice break down of a definitely not so trivial topic
Which color scheme are you using in the VS screenshot?
Hi Chris,
just wanna say hi and let you know that I share this article in the latest C# Digest (https://csharpdigest.net/digests/251) and the audience really liked it.
Keep up the writing! And feel free to get in touch.
Jakub
Can you comment on some best practices like not marking every method async down the chain? Imagine controller -.> service -> repository. You should just return tasks all the way up and only add async to the controller action right?
Thank you very much
It’s helpful article