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?

  • 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...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 9 '16 at 13:58
up vote 16 down vote accepted

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.

  • 2
    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
  • @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
  • 1
    See my answer...the continuation is marshalled back to the original thread by default. – Michael Brown Aug 11 '16 at 20:24
  • 2
    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<ActionResult> Details(int? id). Otherwise, the original call will just block, waiting for department == null to resolve. – Robert Harvey Aug 11 '16 at 20:29

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 no sense in this. 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.

  • Hm, that's less about cores / threads and more about using asynchronous calls properly. – Deduplicator Aug 5 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 5 at 13:00

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.

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.

  • It's more like "I reserve the right to handle the message asynchronously, use this for getting the result". – Deduplicator Aug 5 at 12:52

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.

  • 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
  • 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
  • 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<ActionResult> must also be awaited. – Robert Harvey Aug 11 '16 at 20:25
  • 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. This includes actual output demonstrating that a new thread is not created. – ToolmakerSteve Jun 27 at 4:58

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.