There is some language around async/await that I am going to stop using. I’ve heard others use it as well because it does help get the point across but I believe it is ultimately misleading. Async/await does not “release the thread.”
To see this you need to look at one level higher in your call stack than where you are awaiting. Consider the following code
1: private void Button_Click(object sender, RoutedEventArgs e)
6: private async Task LoadData()
8: HttpClient client = new HttpClient();
9: string result1 = await client.GetStringAsync(new Uri("http://www.microsoft.com/)"));
10: int count = result1.Length;
11: string result2 = await client.GetStringAsync(new Uri("http://msdn.microsoft.com"));
12: count = result1.Length + result2.Length;
14: MessageDialog messageDialog = new MessageDialog("Found " + count.ToString() + " characters");
15: await messageDialog.ShowAsync();
When people hear “release the thread” as in “when the code gets to the first ‘await’ on line 9, it releases the thread so that the UI can run while the async method happens” it gives the wrong impression that the thread is instantly put back into the thread pool (or something) and is available for arbitrary work to be assigned it.
The next thing it does is not arbitrary. When the execution gets to the await, the async operation is started and the thread returns to the immediate caller. In this case the caller is Button_Click which itself just returns. So no harm no foul right? Wrong. Lets make a minor change.
8: private async Task LoadData()
10: HttpClient client = new HttpClient();
11: string result1 = await client.GetStringAsync(new Uri("http://www.microsoft.com/)"));
12: int count = result1.Length;
13: string result2 = await client.GetStringAsync(new Uri("http://msdn.microsoft.com"));
14: count = result1.Length + result2.Length;
16: MessageDialog messageDialog = new MessageDialog("Found " + count.ToString() + " characters");
17: await messageDialog.ShowAsync();
We haven't changed the method of LoadData, our async method. So it still works as planned. What we changed was the calling method. When the code gets to line 11 and hits the first await, the async operation is started and the thread returns to the caller. In this case we have added code after our call to our async method. That code will execute immediately on the same thread. So while GetStringAsync is executing, DoSomething will execute and then DoSomethingElse. Maybe that’s OK and maybe its not. Just don’t assume that the thread disappears and magically reappears later.
The thread doesn’t go away, it just returns and keeps on truckin’
Sorry but I thought this was "obvious".
@Err to some it may be. But to many developers who don't work with threading or asynchronous methods on a regular basis there is a lot of "magic" that happens here and things don't go as they might expect. This post came out of a conversation I had yesterday with some developers who were new to the async/await model and were thinking that their entire call chain was going to be paused until the async op was completed.
To be clear (to other readers, since the blog post was a bit of a blur to me until I realized I misread and mistook the "keeps on truckin'" reference to suggest plowing forward past line 11 to line 12), the synchronocity behavior retained within the body of LoadData() is isolated to LoadData()'s body while the asynchrony is in the invoking of LoadData() (line 3). Since that entire method is marked as async (line 8), which is a visual flag that invoking the method will essentially put that invocation into an isolated thread during await, then dispatch back to the originating UI thread upon its completion.
Also worth noting that this code would give a warning that the first task isn't awaited. Pay attention to your warnings, or you'll need blog posts like this
I assume the fix would be
Yes, you get the warning if the method returns a Task (watch for green squiggles) but if your async method returns void then you get no warning at all. The general guidance is to only have an async method return void if it is an event handler which helps with that. Thanks!
@peter, yes that would be the fix if we wanted everything to run in series like traditional synchronous code (and that pushes the same issue to the previous caller in the stack). The bigger point is that you actually have a choice because the calling method gets control back when the asynchrony begins. You can await it, or you can take that returned Task and do a higher level of composition with other Tasks, or just ignore it if that is the appropriate behavior.