await has been part of C# since C# 5.0 yet many developers have yet to explore how it works under the covers, which I would recommend for any syntactic sugar in C#. I won’t be going into that level of detail now, nor will I explore the subtleties of IO and CPU bound operations.
The Common Misconception
That awaited code executes in parallel to the code that follows.
i.e. in the following code,
LongOperation() is called and awaited, then while this is executing and before it has completed, the code ‘do other things’ will start being executed.
async Task WithAwaitAtCallAsync()
This is not how it behaves.
In the above code, what actually happens is that the
await operator causes
WithAwaitAtCallAsync() to suspend at that line and returns control back to
DemoAsyncAwait() until the awaited task,
LongOperation(), is complete.
LongOperation() completes, then ‘do other things’ will be executed.
And If We Don't Await When We Call?
Then you do get that behaviour some developers innocently expect from awaiting the call, where
LongOperation() is left to complete in the background while continuing on with
WithoutAwaitAtCallAsync() in parallel, 'doing other things':
async Task WithoutAwaitAtCallAsync()
Task task = LongOperation();
LongOperation() is not complete when we reach the awaited
Task it returned, then it yields control back to
DemoAsyncAwait(), as above. It does not continue to complete 'more things to do' - not until the awaited task is complete.
Complete Console Application Example
Some notes about this code:
- Always use
Task.Wait() to retrieve the result of a background task (outside of this demo) to avoid blocking. I've used
Task.Wait() in my demonstrations to force blocking and prevent the two separate demo results overlapping in time (I know in hindsight, Winforms would have been a better demo).
- I have intentionally not used
Task.Run() as I don't want to confuse things with new threads. Let's just assume
LongOperation() is IO-bound.
- I used
Task.Delay() to simulate the long operation.
Thread.Sleep() would block the thread.
private static void Main(string args)
Console.WriteLine(" Demo 1: Awaiting call to long operation:");
Task withAwaitAtCallTask = WithAwaitAtCallAsync();
Console.WriteLine(" Demo 2: NOT awaiting call to long operation:");
Task withoutAwaitAtCallTask = WithoutAwaitAtCallAsync();
private static async Task WithAwaitAtCallAsync()
Console.WriteLine(" WithAwaitAtCallAsync() entered.");
Console.WriteLine(" Awaiting when I call LongOperation().");
Console.WriteLine(" Pretending to do other work in WithAwaitAtCallAsync().");
private static async Task WithoutAwaitAtCallAsync()
Console.WriteLine(" WithoutAwaitAtCallAsync() entered.");
Console.WriteLine(" Call made to LongOperation() with NO await.");
Task task = LongOperation();
Console.WriteLine(" Do some other work in WithoutAwaitAtCallAsync() after calling LongOperation().");
private static async Task LongOperation()
Console.WriteLine(" LongOperation() entered.");
Console.WriteLine(" Starting the long (3 second) process in LongOperation()...");
Console.WriteLine(" Completed the long (3 second) process in LongOperation()...");
This is what happens when the code is executed (with colouring):
If you use the
await keyword when calling an
async method (from inside an
async method), execution of the calling method is suspended to avoid blocking the thread and control is passed (or yielded) back up the method chain. If, on its journey up the chain, it reaches a call that was not awaited, then code in that method is able to continue in parallel to the remaining processing in the chain of awaited methods until it runs out of work to do, and then needs to
await the result, which is inside the
Task object returned by