33

In this MSDN article, the following example code is provided (slightly edited for brevity):

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

The FindAsync method retrieves a Department object by its ID, and returns a Task<Department>. Then the department is immediately checked to see if it is null. As I understand it, asking for the Task's value in this manner will block code execution until the value from the awaited method is returned, effectively making this a synchronous call.

Why would you ever do this? Wouldn't it be simpler to just call the synchronous method Find(id), if you're going to immediately block anyway?

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • It could be implementation related. `... else return null;` Then you'd need to check that the method actually found the department you asked for. – Jeremy Kato Aug 11 '16 at 20:10
  • I don't see any in an asp.net, but in a destop app, by doing it this way you are not freezing the ui – Rémi Aug 11 '16 at 20:15
  • Here's a link that explain the await concept from the designer...https://msdn.microsoft.com/en-us/magazine/hh456401.aspx – Jon Raynor Aug 11 '16 at 20:35
  • Await is only worth thinking about with ASP.NET if thread contact switches are slowing you down, or the memory usage form many threads stacks is an issue for you. – Ian Nov 09 '16 at 13:58

6 Answers6

32

As I understand it, asking for the Task's value in this manner will block code execution until the value from the awaited method is returned, effectively making this a synchronous call.

Not quite.

When you call await db.Departments.FindAsync(id) the task is sent off and the current thread is returned to the pool for use by other operations. The flow of execution is blocked (as it would be regardless of using department right after, if I understand things correctly), but the thread itself is free to be used by other things while you wait for the operation to be completed off-machine (and signalled by an event or completion port).

If you called d.Departments.Find(id) then the thread does sit there and wait for the response, even though most of the processing is being done on the DB.

You're effectively freeing up CPU resources when disk bound.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • 3
    But I thought all `await` did was sign on the remainder of the method as a continuation on the same thread (there are exceptions; some async methods spin up their own thread), or sign on the `async` method as a continuation on the same thread and allow the remaining code to execute (as you can see, I'm not crystal-clear on how `async` works). What you're describing sounds more like a sophisticated form of `Thread.Sleep(untilReturnValueAvailable)` – Robert Harvey Aug 11 '16 at 20:16
  • 3
    @RobertHarvey - it does assign it as a continuation, but once you've sent the task (and its continuation) off to be processed, there's nothing left to run. It's not guaranteed to be on the same thread unless you specify it (via `ConfigureAwait` iirc). – Telastyn Aug 11 '16 at 20:21
  • So what happens when it hits `department == null`? Doesn't whatever thread I'm on now have to wait for the return value at that point? I just don't see what that buys me, unless you're telling me that there is code running in an entirely different place outside of this method that benefits from this arrangement. – Robert Harvey Aug 11 '16 at 20:22
  • 2
    See my answer...the continuation is marshalled back to the original thread by default. – Michael Brown Aug 11 '16 at 20:24
  • 3
    I think I see what I'm missing here. In order for this to provide any benefit, the ASP.NET MVC framework must `await` its call to `public async Task Details(int? id)`. Otherwise, the original call will just block, waiting for `department == null` to resolve. – Robert Harvey Aug 11 '16 at 20:29
  • 3
    @RobertHarvey By the time `await ...` "returns" the `FindAsync` call has finished. That's what await does. It's called await because it makes your code wait for things. (But note that is not the same as making the current thread wait for things) – user253751 Aug 11 '16 at 23:27
  • @RobertHarvey The code queries the database, which doesn't need a thread, so it can go back to the threadpool to handle other operations/requests. If you specify `ConfigureAwait(false)`, the method will grab a random thread from the threadpool to continue when the query has finished executing. If you don't specify `ConfigureAwait`, it should attempt to grab the same thread again which is slightly less efficient. – Alexander Derck Aug 18 '16 at 18:14
  • @AlexanderDerck: http://programmers.stackexchange.com/questions/328203/why-would-you-ever-await-a-method-and-then-immediately-interrogate-its-return/328206?noredirect=1#comment697415_328206 – Robert Harvey Aug 18 '16 at 18:15
  • @RobertHarvey Just wanted to stress it's important that the operation is truly async because in this case there's an IO operation going on, not trying to be a smartass or anything :-) – Alexander Derck Aug 18 '16 at 18:19
  • @AlexanderDerck: Well, "slightly less efficient" is debatable. Additional threads have overhead, and if the async method is running in a tight servicing loop (e.g. Node), it's better to *sign onto the same thread as a continuation and immediately return.* Once that happens, the original servicing thread can spin up new threads as needed for background tasks. – Robert Harvey Aug 18 '16 at 18:21
  • The task isn't "sent off". This is concurrency, not parallelism. The task still executes until it is blocked by something like I/O, waiting for a response from your bank's database, etc. In such a case, the thread then continues work on the next process in the line of execution until the original called async function is no longer blocked. This all happens on one thread. – 8protons Apr 12 '18 at 22:19
25

I really hate that none of the examples show how it’s possible to wait a few lines before awaiting the task. Consider this.

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

This is the kind of code the examples encourage, and you’re right. There’s little sense in this. It does free the main thread to do other things, like respond to UI input, but the real power of async/await is I can easily keep doing other things while I’m waiting for a potentially long running task to complete. The code above will “block” and wait to execute the print line until we’ve gotten Foo & Bar. There’s no need to wait though. We can process that while we wait.

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

Now, with the rewritten code, we don’t stop and wait for our values until we have to. I’m always on the lookout for these kinds of opportunities. Being smart about when we await can lead to significant performance improvements. We’ve got multiple cores these days, might as well use them.

RubberDuck
  • 8,911
  • 5
  • 35
  • 44
  • 3
    Hm, that's less about cores / threads and more about using asynchronous calls properly. – Deduplicator Aug 05 '18 at 12:48
  • You’re not wrong @Deduplicator. That’s why I quoted “block” in my answer. It’s hard to speak to this stuff without mentioning threads, but while still acknowledging there may or may not be multi-threading involved. – RubberDuck Aug 05 '18 at 13:00
  • I suggest you be careful with awaiting inside another method call.. Sometimes compiler misunderstands this await and you get a really weird exception. After all async/await is just syntax sugar, you can check with sharplab.io how the generated code looks. I observed this on several occasions and now I just await one line above the call where result is needed... don't need those headaches. – evictednoise Mar 05 '19 at 15:23
  • 3
    "There’s little sense in this." - There's lots of sense in this. The cases where "keep doing other things" is applicable in the very same method are not that common; it's far more common that you just want to `await` and let the thread do completely different things instead. – Sebastian Redl Sep 04 '19 at 06:31
  • When I said “there’s little sense in this” @SebastianRedl, I meant the case where you could obviously run both both tasks in parallel rather than run, wait, run, wait. This is far more common than you seem to think it is. I bet if you looked around your code base you’d find opportunities. – RubberDuck Sep 04 '19 at 10:25
6

So there's more that happens behind the scenes here. Async/Await is syntactic sugar. First look at the signature of the FindAsync function. It returns a Task. Already you're seeing the magic of the keyword, it unboxes that Task into a Department.

The calling function does not block. What happens is that the assignment to department and everything following the await keyword get boxed into a closure and for all intents and purposes passed to the Task.ContinueWith method (the FindAsync function is automatically executed on a different thread).

Of course there is more that happens behind the scenes because the operation is marshalled back to the original thread (so you no longer have to worry about synchronizing with the UI when performing a background operation) and in the case of the calling function being Async (and being called asynchronously) the same thing happens up the stack.

So what happens is you get the magic of Async operations, without the pitfalls.

Michael Brown
  • 21,684
  • 3
  • 46
  • 83
1

No it's not returning immediately. The await makes the method call asynchronous. When FindAsync is called, the Details method returns with a task which is not finished. When FindAsync finishes, it will return its result into the department variable and resume the rest of the Details method.

Steve
  • 171
  • 3
  • No there is no blocking at all. It will never get to department == null until after the async method is already finished. – Steve Aug 11 '16 at 20:12
  • Then how will it know what to return? – Robert Harvey Aug 11 '16 at 20:12
  • How will what know? – Steve Aug 11 '16 at 20:13
  • `if (department == null) { return HttpNotFound(); }` – Robert Harvey Aug 11 '16 at 20:13
  • At that point the code's executing in a new thread. The original thread never passes the `await()` call, that call spawns a new thread which continues waiting for the result of the `FindAsync()` call while in the original thread it returns a task object in the unfinished state. When the new thread gets the results from `FindAsync()` it continues executing the code after the `await()` call and returns it's result, which gets attached to the task object which switches to the finished state. The original thread can then be notified of the switch and collect the result. – Todd Knarr Aug 11 '16 at 20:16
  • 1
    `async` `await` doesn't generally create new threads, and even if it did, you still need to wait for that department value to find out if it's null. – Robert Harvey Aug 11 '16 at 20:18
  • I'm sure FindAsync is returning a Task<> object, because its being awaited. So it would be on its' own thread. Feel free to set a breakpoint at (department == null). It will not be reached until after FindAsync is complete. – Steve Aug 11 '16 at 20:20
  • 2
    So basically what you're saying is that, in order for this to be of any benefit at all, the call to `public async Task` *must also be `await`ed.* – Robert Harvey Aug 11 '16 at 20:25
  • Yes the MVC framework does Await the invocation of a controller if it is async. – Michael Brown Aug 11 '16 at 20:30
  • 2
    @RobertHarvey Yes, you've got the idea. Async/Await is essentially viral. If you "await" one function, you should also await any functions that call it. `await` shouldn't be mixed with `.Wait()` or `.Result`, as this can cause deadlocks. The async/await chain typically ends at a function with an `async void` signature, which is mostly used for event handlers, or for functions invoked directly by UI elements. – KChaloux Aug 12 '16 at 12:50
  • 1
    @Steve - "*... so it would be on its own thread.*" No, read [Tasks are still not threads and async is not parallel](https://blogs.msdn.microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel/). This includes actual output demonstrating that a new thread is not created. – ToolmakerSteve Jun 27 '18 at 04:58
  • ... actually, later on that link shows that while that is the *default* behavior, it is possible to get *the continuations* to run on different threads, resulting in overlapped execution (parallelism). At least in WPF - but not guaranteed. BUT the final screenshot shows that the awaited task must actually be async internally - if not, there is no overall time savings. Which surprised me - not how I thought that would work. Bottom line: use `await Task.Run` to force parallelism, if awaiting pure cpu work. – ToolmakerSteve Jun 27 '18 at 05:09
1

I like to think of "async" like a contract, a contract that says "i can execute this asynchronously if you need it to, but you can call me like any other synchronous function as well".

Meaning, one developer was making functions and some design decisions led them to make/mark a bunch of functions as "async". The caller/consumer of the functions is at the liberty to use them as they decide. As you say you can either call await right before the function call and wait for it, this way you have treated it like a synchronous function, but if you wanted to, you can call it without await as

Task<Department> deptTask = db.Departments.FindAsync(id);

and after, say, 10 lines down the function you call

Department d = await deptTask;

hence treating it as an asynchronous function.

Its upto you.

user734028
  • 127
  • 2
  • 1
    It's more like "I reserve the right to handle the message asynchronously, use this for getting the result". – Deduplicator Aug 05 '18 at 12:52
  • Awaiting immediately is not really a synchronous call, if you do that on say an UI thread you will remain responsive. This is a synchronous (and dangerous) call `Department d = db.Departments.FindAsync(id).Result;` and it will cause the UI to freeze until it returns. (Be warned that using `.Result` like that can introduce dead-locks and should almost always be avoided unless you know the task is actually done.) – AnorZaken Mar 30 '20 at 16:00
-1

"if you're going to immediately block anyway" the anwser is "Yes". Only when you need a fast response, await/async make sense. For example UI thread come to a really async method, UI thread will return and continue to listen to button click, while code below "await" will be excuted by other thread and finally get the result.