41

I would like to get some clarification on what is the added benefit of using of Await and Async all the way down.

If my application is calling await Func1() (So no blocking to the UI here). and Func1 is calling await Func2(), but the results from Func2() are important for Func1 to complete it's job, then why would I need to make Func2() awaitable. Func1() execution will take just as long because it's waiting on Func2 to finish. All what the await is doing here is adding the StateMachine overhead.

Am I missing something here?

i3arnon
  • 113,022
  • 33
  • 324
  • 344
Alaeddin Hussein
  • 738
  • 1
  • 7
  • 14

5 Answers5

49

A better slogan is async all the way up. Because you start with an asynchronous operation and make its caller asynchronous and then the next caller etc.

You should use async-await when you have an inherently asynchronous operation (usually I/O but not necessarily) and you don't want to waste a thread idly waiting for the operation to complete. Choosing an async operation instead of a synchronous one doesn't speed up the operation. It will take the same amount of time (or even more). It just enables that thread to continue executing some other CPU bound work instead of wasting resources.

But to be able to await that operation the method needs to be an async one and the caller needs to await it and so forth and so forth.

So async all the way up enables you to actually make an asynchronous call and release any threads. If it isn't async all the way then some thread is being blocked.

So, this:

async Task FooAsync()
{
    await Func1();
    // do other stuff
}

async Task Func1()
{
    await Func2();
    // do other stuff
}

async Task Func2()
{
    await tcpClient.SendAsync();
    // do other stuff
}

Is better than this:

void Foo()
{
    Func1();
    // do other stuff
}

void Func1()
{
    Func2().Wait();  // Synchronously blocking a thread.
    // do other stuff
}

async Task Func2()
{
    await tcpClient.SendAsync();
    // do other stuff
}
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 13
    So unless you await all the way up, there's no benefit. – Ian Warburton Feb 17 '17 at 15:58
  • So, I tried the `Func2().Wait();` and the execution froze (it was ok with `async-await` though). I'm not sure I follow it, but are there alternatives? I mean, if I'm going to wait, why making it async anyway? – Daniel Möller Feb 17 '20 at 15:10
14

The major benefit is the fact that awaiting an asynchronous method returns the worker thread to the pool to be used in other calls (web requests to your .NET MVC web app, for example). The asynchronous work is done on an IO completion thread. When the awaited method finishes, another worker thread will collect the results and resume execution. This prevents worker thread pool exhaustion and allows your application to handle more load (CPU, memory, and network throughput depending).

As for "await all the way down", that seems like an issue to me. Typically await is associated to an external resource (DB call, HTTP request, etc.) that your application must wait on. If you await code that doesn't have external IO dependencies, you're creating overhead that isn't needed. It's possible to have multiple awaits in an async method chain, but awaiting some code that itself calls await but has no other external IO dependency is not good and will just add callback/compiler overhead.

Haney
  • 32,775
  • 8
  • 59
  • 68
4

Usually, the reasoning behind async/await goes the other way around:

For whatever reason, you decide that Func2 would be easier to write using await. So you simplify the method by adding the desired awaits, meaning you also have to change the method signature (it will now include the async keyword and a return type of Task or Task<T>).

Because the return type has changed, you can no longer call Func2 like you used to (var result = Func2();), so you're now required to change the calling method, Func1. The easiest way to adapt Func1 will often be to make it async too, and await Func2().

Then, for the same reason (changed signature), you will need to change all calls to Func1, and so on until you get to some kind of entry point (either a UI event handler, or your Main method, or something else).

So you don't start making the "outermost" method async and follow through to the "inner" (called) methods; you usually make things async while going in the opposite direction (from the "innermost" methods back to the calling ones). This other answer calls this idea "async all the way up".

Community
  • 1
  • 1
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • 16
    so it's like a cancer then. – matao Aug 31 '17 at 02:46
  • @matao: If you think that `async`/`await` is a bad thing, then, perhaps, yes. I for one think that it leads to much nicer code than the older, alternative async programming patterns, so from my perspective comparing it with a disease is unwarranted. And then of course not everything should be async in the first place. There's a lot of situations where it simply doesn't make much sense to introduce asynchrony / concurrency, so `async`/`await` won't by itself spread everywhere. – stakx - no longer contributing Sep 09 '17 at 11:32
  • 3
    (too long so had to break it up) [1] I was exaggerating a bit of course, but it seems to me that if you're not careful then you could easily end up with every single call in your codebase being async/await, especially if you have junior devs who don't understand what it's for and when not to use it. – matao Sep 11 '17 at 07:28
  • [2] I think it's a bit of a leaky abstraction, and isn't a high enough level tool to deal with concurrency properly. eg, if I'm using a repository, I don't want to care at the code level (although I will at the architecture level) whether it's backed by a web service, a database, or an in-memory Dictionary. But the web service will leak async/await back into my call stack. Also, database calls are also going over the network, but EF5 (correctly) doesn't force you to use async/await. – matao Sep 11 '17 at 07:28
  • [3] And what if I want to fire off multiple requests and .Join() to wait for all of them to complete? myabe I'm missing something but that doesn't seem simple with this idiom. I dunno, I think I/O blocking threading should be able to be handled by the direct caller, so that it's transparent to the majority of your code. – matao Sep 11 '17 at 07:28
  • 2
    @matao: I don't think the comment section offers enough room to debate the general merits and shortcomings of `async`/`await`, perhaps having a chat would be more appropriate. Just briefly: **(1)** I suspect `async`/`await` was mainly introduced to help programmers create responsive UIs. In early .NET, you had to do explicit multithreading and callbacks, which wasn't easy. Many people got this wrong (i.e. not getting back to the UI thread before updating UI controls). `async`/`await` makes *this scenario* a lot easier. But indeed it doesn't cover all multi-threading scenarios such as your [3]. – stakx - no longer contributing Sep 11 '17 at 10:06
  • 2
    **(2)** Given your [1] and esp. [2], if you really don't want to care whether something is implemented sync or async, then the logical conclusion for me would be to hide this implementation detail by making *everything* async on the surface. This becomes feasible with `ValueTask`, which being a `struct` (unlike `Task`) is lightweight enough to not be too much overhead in the case of a method that is actually completely blocking / synchronous. But yes, designing an API 100 % async (even when not necessary) would be overkill IMHO. – stakx - no longer contributing Sep 11 '17 at 10:10
  • **(3)** You could in theory implement a `.Join` operation on several `Task`s by creating an `await`-able type that represents a tuple of `Task`s. In fact you'd need two such tuple types, one whose awaiter is functionally equivalent to `Task.WhenAny` and the other functionally equivalent to `Task.WhenAll`. – stakx - no longer contributing Sep 11 '17 at 10:12
  • In closing, I agree `async`/`await` isn't perfect, but for many common programming tasks, it's the simplest and cleanest programming pattern we've had so far in .NET. – stakx - no longer contributing Sep 11 '17 at 10:13
  • fair enough, I think for the lowest common denominator dev, it probably works well enough most of the time. Personally I don't like it and would much prefer to use the concurrency libs directly like TPL. But I can live with it. cheers! – matao Sep 13 '17 at 03:15
3

"This async method lacks 'await' operators and will run synchronously" is what happens if you don't await an async method. To harness the benefits of an async method you must await it, turning the caller into an async method which can be awaited etc etc.

From the flow diagram you see that the async method returns a Task (a promise of work to be completed in the future) and yields control to it's caller. The caller can then get on with work not dependent on this result in the meantime. Clearly this needs to bubble up the call stack to find all of this gainful work that can be done without the result (In a UI app this work would include unblocking the UI, which is why it's async all the way up to the the event handler in the below example).


From my initial misreading. So you've found some async code that you need to call, is it worth the async await pattern spreading up your code:

I've heard a lot that the main problem with async-await is it's a much too easy syntax for what it actually does. Program flow gets complicated. I really like async-await, but unfortunately in most the async code I've seen it isn't worth it and is just needlessly ruining my call-stack.

A good thing to keep in mind is the 50ms rule.

"This is the rule that Microsoft followed with the WinRT APIs; anything taking less than 50ms is considered “fast” and close enough to “immediate” that they do not require an asynchronous API."

This is being used in the context of encouraging async-await. However, I think it equally should be applied to tell developers using async-await for essentially immediate functionality to cut it out.


https://msdn.microsoft.com/en-us/cc300389.aspx#E

Nathan Cooper
  • 6,262
  • 4
  • 36
  • 75
  • 1
    a good resource on async await is here - http://blog.stephencleary.com/2012/02/async-and-await.html – Shaun Wilde Apr 22 '15 at 21:25
  • 1
    @ShaunWilde Great resource. There's also nothing like a bit of [Stephen Toub](https://msdn.microsoft.com/en-us/magazine/hh456402.aspx) – Nathan Cooper Apr 22 '15 at 21:27
  • @ShaunWilde That's because this has nothing to do with the question. It's just a blob of text, even if it's correct. – i3arnon Apr 23 '15 at 06:12
0

In a UI based application async await provides a very succinct way to handle asynchronous calls resulting in UI updates. If the 'top level' handler from the UI is awaiting a result then there is potentially no real benefit in the whole chain down doing the same unless it makes sense to do so. A design goal of async await was to make async programming look more synchronous and continuous and not have callbacks scattered around etc - making async coding more assessable. It's not something you need to use everywhere arbitrarily.

James Lucas
  • 2,452
  • 10
  • 15