2

Async hive mind: Consider this

private static async Task A()
{
    await DelayOneSecond();
    await DelayOneSecond();
    await DelayOneSecond();
}

private static async Task B()
{
    await Task.WhenAll(DelayOneSecond(), DelayOneSecond(), DelayOneSecond());
}

private static Task DelayOneSecond() => Task.Delay(1000);

Which method will complete first? A, B or at the same time?

The code that runs them simultaneously is missing but imagine that bit is there.

My original answer was that await is a not a blocking operation and based on this similar answer here, these two are similar minus the difference that WhenAll propagates all exceptions at once making it easier not to lose exceptions and also WhenAll will not return immediately if one of the methods throw an exception.

However I was told later that the answer is B, B will complete first because it runs them in parallel. A will run them one after the other waiting for each one to complete before moving on to the next. I was also told: Await is non-blocking, it hands control back to the caller, the rest of the method is setup as a continuation so went execute until after the awaited call has completed.

I've been trying to find more info about this behaviour because I had assumed multiple awaits would work the same way. Can someone give more explanation on the answer

Nick
  • 2,877
  • 2
  • 33
  • 62
  • 1
    Pardon for stating something that maybe stupid, but I don't see how any of them could ever complete, since they don't seem to be called anywhere. – Pac0 Oct 13 '17 at 13:34
  • Right, so the code that runs them in Parallel is missing, but you can imagine that, right? – Nick Oct 13 '17 at 13:35
  • ok, thanks for clarifying. – Pac0 Oct 13 '17 at 13:35
  • 1
    Why don't you try it and see for yourself? – xxbbcc Oct 13 '17 at 13:36
  • I am planning to, didn't have time to write the task that runs them in parallel yet. This is, for now, a theoretical question – Nick Oct 13 '17 at 13:37
  • @xxbbcc I guess that a parallel execution would depend on many factors, and a "trial and conclude" approach is likely to lead to wrong conclusions, or at least not taking into account every cases. – Pac0 Oct 13 '17 at 13:38
  • Just call `A()` and `B()` - why would you need a "task that runs them in parallel"? Put a `Console.WriteLine()` in each call and see what happens. – xxbbcc Oct 13 '17 at 13:38
  • @Pac0 No, this is a pretty clear-cut scenario. :) – xxbbcc Oct 13 '17 at 13:39
  • If this is a theoretical question, do you want a theoretical answer ;) – Jamiec Oct 13 '17 at 13:39
  • @xxbbcc ow, I see it now. Right. :) – Pac0 Oct 13 '17 at 13:40
  • I have now written a full test in a Console App and B always finishes first. I expected that, since I do believe the answer I was given is correct. However the question remains, why? Something so fundamental seems to be missing from so many articles online? – Nick Oct 13 '17 at 13:42
  • This is a compiler question, which make change between compiler versions. Presumably the compiler is smart enough to detect no data dependencies and executes the arguments in parallel. – BurnsBA Oct 13 '17 at 13:43
  • See the answer in the marked duplicate, with the exception of `WaitAll` (which you did not ask about) this perfectly sums up what you are asking. – Igor Oct 13 '17 at 13:45
  • 1
    Try it out here: http://rextester.com/XDRXI67199 – Jamiec Oct 13 '17 at 13:48
  • 1
    @BurnsBA This is not a compiler question. – xxbbcc Oct 13 '17 at 13:49
  • Sorry for not being clearer. The question "Does a function evaluate its arguments in order or in parallel?" is authoritatively answered by the compiler, and is explained in the language reference 7.5.1.2. FWIW, arguments are evaluated in order. – BurnsBA Oct 13 '17 at 14:21

1 Answers1

1

Since the A method have 3 sequential await statements, the 2nd delay has to wait on the 1st one, and the 3rd has to wait on the 2nd one, it will take at least 3 seconds to execute A(). (sequential approach)

The B method could dispatch the Tasks in different threads, so each one doesn't have to wait on the the others. It will take at least one second, and is likely to take much less than 3 seconds.

Pac0
  • 21,465
  • 8
  • 65
  • 74
  • But the fact that await is non-blocking means that all 3 should start at the same time? But it seems that they run synchronously after all – Nick Oct 13 '17 at 13:44
  • await does not block the overall application, but having `await` statements sequentially means precisely that they will be executed sequentially, so synchronously. Or maybe I haven't understood how `await` works at all. – Pac0 Oct 13 '17 at 13:46
  • @Nick - `await` is non-blocking in terms of it's a way to declare "the current method cannot make any progress until *this awaitable* is finished - see if my calling code wants to make use of the current thread because I have no use for it" – Damien_The_Unbeliever Oct 13 '17 at 13:49
  • 1
    You are correct @Pac0. `await` will give back control to the caller. A little bit like how `yield return` works. – Magnus Oct 13 '17 at 13:49
  • @Nick `await` does not block but does enforce ordering. When you await, your function returns (not blocks) and then resumes when the awaited function completes. `Task.WhenAll()` doesn't specify ordering so those calls may happen in parallel. – xxbbcc Oct 13 '17 at 13:50
  • 1
    as @Damien_The_Unbeliever wrote, the thread is free to execute other stuff in the meantime - it's usually useful if underlying operation is async I/O, then you can achieve better throughput because your I/O operations are non blocking. Please take a look here: https://blog.stephencleary.com/2013/11/there-is-no-thread.html. – krlm Oct 13 '17 at 13:54
  • Okay I am satisfied with the answers. I got await wrong all along! So good to know – Nick Oct 13 '17 at 14:01