0

This is a question on an answer given in this question:

How and When to use `async` and `await`

The answer was this: https://stackoverflow.com/a/31479502/1164004

To have a complete question I will also copy paste the answer:

Here is a quick console program to make it clear to those who follow. The "TaskToDo" method is your long running method that you want to make async. Making it run Async is done by the TestAsync method. The test loops method just runs through the "TaskToDo" tasks and runs them Async. You can see that in the results because they don't complete in the same order from run to run - they are reporting to the console UI thread when they complete. Simplistic, but I think the simplistic examples bring out the core of the pattern better than more involved examples:

    class Program
    {
        static void Main(string[] args)
        {
            TestLoops();
            Console.Read();
        }

        private static async void TestLoops()
        {
            for (int i = 0; i < 100; i++)
            {
                await TestAsync(i);
            }
        }

        private static Task TestAsync(int i)
        {
            return Task.Run(() => TaskToDo(i));
        }

        private async static void TaskToDo(int i)
        {
            await Task.Delay(10);
            Console.WriteLine(i);
        }
    }

Now for my part: As for my understanding, an await call means that if the task is over, the code will continue. if its not, it will return to the calling context until the task finishes. If that's the case then why isn't it correct to say that in the program above, the first iteration of the loop will return to the main method's context until the task is finished and only then will the next iteration of the for loop be carried out and continue in that manner for all iteration of the loop? If it waits for the first one to end and then the 2nd and then 3rd and so on, how can it be that the tasks will not finish in the natural order?

CodeMonkey
  • 11,196
  • 30
  • 112
  • 203
  • 3
    The main issue here is the use of `async void TaskToDo()`. That is a silly, unproductive pattern. It makes this whole example meaningless. – H H Jul 08 '17 at 14:51
  • 1
    Rewrite it with `async static Task TaskToDo(int i)` , return a value and make TestLoops() add up those results. You will then have a true async program and all tasks will execute in order. – H H Jul 08 '17 at 14:59
  • Why will returning a task changes anything? The TestAsync is calling Task.Run and not await Task.Run – CodeMonkey Jul 08 '17 at 15:16
  • 1
    Try it and learn. A here is a much better better [resource](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) – H H Jul 08 '17 at 15:29
  • 1
    Long story short, `TaskToDo` is not awaited, so the task created by `Task.Run` is finished before the code in `TaskToDo` is. – juharr Jul 08 '17 at 15:37
  • @HenkHolterman if you think that Oleh's answer is not correct or not accurate, I'll be glad if you could also add an answer – CodeMonkey Jul 08 '17 at 16:42
  • 1
    The question is "Why ..." and my best answer is "Don't". Read the article I linked to. – H H Jul 08 '17 at 17:33
  • Your question is just like all the other "why doesn't the code wait for this task to complete?" questions. Older questions will involve the `ContinueWith()`, newer ones will involve `await`, which is a new(er) C# feature that makes it simpler to use `ContinueWith()`. In either case, as already explained to you, the `TaskToDo()` method returns as soon as it needs to wait for something (i.e. `await` or `ContinueWith()` on something that itself hasn't completed immediately). This makes a naïve caller (like `Task.Run()`) believe the method is done (because it is, for now). – Peter Duniho Jul 08 '17 at 19:11

1 Answers1

0

For your code the execution will flow like this:

  1. Enter the loop (iteration 0)
  2. Call TestAsync(0)
  3. Create a new task (()=>TaskToDo(0))
  4. The task calls to Task.Delay.
  5. Because the delay task is not completed, the control is returned to the caller (the lambda).
  6. Lambda completes, and it fires the task you created in step 3.
  7. This task is now completed and loop goes forward, creating new tasks.

Then the await Task.Delay code starts to work when delays complete, causing random ordered output to console.

Oleh Nechytailo
  • 2,155
  • 17
  • 26
  • Tell me if I'm correct - in step 4, since the Task.Delay is called with await, the control is returned to the lambda like you said in 5. This means we're actually finishing the return statement which then completes the the task of TestAsync and thus the for loop continues to the next iteration even though (in some cases) the code hasn't reached the console.writeline? and then the next iteration can print the number before the previous task did? – CodeMonkey Jul 08 '17 at 15:09
  • 1
    @Oleh - Not really. The control is already returned to the caller after step 3. Steps 4 through 7 happen completely detached on a pool thread. – H H Jul 08 '17 at 15:10
  • 1
    @YonatanNir, yes, in the current code nobody actually waits for `Task.Delay`, it happens completely detached from the main loop. You need to pass task from the `TaskToDo` to the main loop. And yes, I misused terminology about `control return`. – Oleh Nechytailo Jul 08 '17 at 16:17