Click here to Skip to main content
15,166,980 members
Articles / Programming Languages / C#
Technical Blog
Posted 10 Feb 2018

Tagged as

Stats

55.3K views
171 downloads
65 bookmarked

Addressing a Simple Yet Common C# Async/Await Misconception

Rate me:
Please Sign up or sign in to vote.
4.72/5 (41 votes)
19 Feb 2018CPOL2 min read
I regularly come across developers who hold the misconception that code in a method will continue to be executed, in parallel to code in an awaited method call. So I'm going to demonstrate the behaviour we should expect in this article.

Async/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.

C++
void DemoAsyncAwait()
{
    WithAwaitAtCallAsync().Wait();
}

async Task WithAwaitAtCallAsync()
{
    await LongOperation();

    // do other things
}

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.

When 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':

C#
void DemoAsyncAwait()
{
    WithoutAwaitAtCallAsync().Wait(); 
} 

async Task WithoutAwaitAtCallAsync() 
{ 
    Task task = LongOperation(); 

    // doing other things 
   
    await task;

    // more things to do
}

However, if 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 await over 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.
C#
private static void Main(string[] args)
{
    // Demo 1
    Console.WriteLine(" Demo 1: Awaiting call to long operation:");
    Task withAwaitAtCallTask = WithAwaitAtCallAsync();
    withAwaitAtCallTask.Wait();

    // Demo 2
    Console.WriteLine(" Demo 2: NOT awaiting call to long operation:");
    Task withoutAwaitAtCallTask = WithoutAwaitAtCallAsync();
    withoutAwaitAtCallTask.Wait();

    Console.ReadKey();
}

private static async Task WithAwaitAtCallAsync()
{   
    Console.WriteLine(" WithAwaitAtCallAsync() entered.");

    Console.WriteLine(" Awaiting when I call LongOperation().");
    await 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().");

    await task;
}

private static async Task LongOperation()
{
     Console.WriteLine(" LongOperation() entered.");

     Console.WriteLine(" Starting the long (3 second) process in LongOperation()...");
     await Task.Delay(4000);
     Console.WriteLine(" Completed the long (3 second) process in LongOperation()...");
}

This is what happens when the code is executed (with colouring):

GiF of example

Summary

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 LongOperation().

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

BenHall_io
Software Developer (Senior)
United Kingdom United Kingdom
Ben is the Technical Lead for C# .NET at a gov.uk and .NET Foundation foundation member. He previously worked for over 8 years as a school teacher, teaching programming and Computer Science. He enjoys making complex topics accessible and practical for busy developers.


Comments and Discussions

 
GeneralRe: Does it really work in a multi-threaded ASP.NET application? Pin
Hmmkk14-Feb-18 6:05
MemberHmmkk14-Feb-18 6:05 
GeneralRe: Does it really work in a multi-threaded ASP.NET application? Pin
George Swan16-Feb-18 22:06
MemberGeorge Swan16-Feb-18 22:06 
GeneralRe: Does it really work in a multi-threaded ASP.NET application? Pin
Hmmkk19-Feb-18 1:44
MemberHmmkk19-Feb-18 1:44 
GeneralRe: Does it really work in a multi-threaded ASP.NET application? Pin
George Swan19-Feb-18 6:01
MemberGeorge Swan19-Feb-18 6:01 
GeneralRe: Does it really work in a multi-threaded ASP.NET application? Pin
Hmmkk23-Mar-18 13:06
MemberHmmkk23-Mar-18 13:06 
AnswerRe: Does it really work in a multi-threaded ASP.NET application? Pin
ipavlu23-Mar-18 9:39
Memberipavlu23-Mar-18 9:39 
GeneralRe: Does it really work in a multi-threaded ASP.NET application? Pin
Hmmkk23-Mar-18 13:15
MemberHmmkk23-Mar-18 13:15 
GeneralRe: Does it really work in a multi-threaded ASP.NET application? Pin
ipavlu23-Mar-18 14:20
Memberipavlu23-Mar-18 14:20 
I have a strong feeling, that you may count with some async/await behavior that behaves differently in the debug code and in production runs freely, what can generate unseen behavior in debug mode.

There is one thing in your first reply, some expectation I believe may cause unsupported expectation.

Quote:
I find it really weird, since here and elsewhere it's often stated that await makes the program really wait before going further in the code, but that doesn't seem to apply to my website


This is not actually true.
Task task.

task.Wait() is waiting, that means the thread is suspended and resumed when task is completed.

await task is not waiting thus the program / execution of other threads on thread pool is not stopped.
What await does is attaching continuation on the task and that continuation is executed immediately/synchronously if the task is already completed and if not then the code is returning back to the caller of the async method and the method is returning a result as task, which will be set to completed state when the continuation will be completed. Here the continuation is everything after that await, that is what async/await methods do for us.

Now, it is possible, that in debug mode the db objects are still present, but in production, the timing is different, threads already executed and freed some resources necessary for successful execution of the method in the qustion?

Threading issues are pretty complex problems, sometimes async/await makes it worse as some subtle details are now followed. I would start analyzing the problem based on what resources are necessary to make the call successful. If you know, that based on data in db it should not be null, then you have an issue with the timing of data access and handling time scope when all necessary data are available and valid.

You have to dig deeper into what is going around that method.
When the db objects are created and who, where, when and in what order are these data removed, disposed.

It is not caused by awaiting itself.
GeneralMy vote of 4 Pin
KGustafson13-Feb-18 9:27
professionalKGustafson13-Feb-18 9:27 
GeneralRe: My vote of 4 Pin
BenHall_io14-Feb-18 7:02
professionalBenHall_io14-Feb-18 7:02 
QuestionWill this work with a UI Thread? Pin
George Swan12-Feb-18 6:43
MemberGeorge Swan12-Feb-18 6:43 
AnswerRe: Will this work with a UI Thread? Pin
BenHall_io12-Feb-18 7:20
professionalBenHall_io12-Feb-18 7:20 
GeneralRe: Will this work with a UI Thread? Pin
George Swan12-Feb-18 8:12
MemberGeorge Swan12-Feb-18 8:12 
GeneralRe: Will this work with a UI Thread? Pin
Member 1302925212-Feb-18 23:02
MemberMember 1302925212-Feb-18 23:02 
GeneralRe: Will this work with a UI Thread? Pin
George Swan13-Feb-18 8:47
MemberGeorge Swan13-Feb-18 8:47 
GeneralRe: Will this work with a UI Thread? Pin
Stephen Russell MCT14-Feb-18 2:35
professionalStephen Russell MCT14-Feb-18 2:35 
GeneralRe: Will this work with a UI Thread? Pin
George Swan14-Feb-18 4:06
MemberGeorge Swan14-Feb-18 4:06 
QuestionOK couple of things Pin
Sacha Barber11-Feb-18 11:43
MemberSacha Barber11-Feb-18 11:43 
AnswerRe: OK couple of things Pin
BenHall_io12-Feb-18 2:46
professionalBenHall_io12-Feb-18 2:46 
GeneralRe: OK couple of things Pin
wkempf13-Feb-18 4:50
Memberwkempf13-Feb-18 4:50 
GeneralRe: OK couple of things Pin
BenHall_io13-Feb-18 7:36
professionalBenHall_io13-Feb-18 7:36 
QuestionGood Tip Pin
Dirk Bahle11-Feb-18 8:14
MemberDirk Bahle11-Feb-18 8:14 
AnswerRe: Good Tip Pin
BenHall_io12-Feb-18 9:17
professionalBenHall_io12-Feb-18 9:17 
AnswerRe: Good Tip Pin
wkempf13-Feb-18 4:58
Memberwkempf13-Feb-18 4:58 
GeneralRe: Good Tip Pin
Dirk Bahle14-Feb-18 6:20
MemberDirk Bahle14-Feb-18 6:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.