-1

I have the following code:

  static void Main(string[] args)
    {
        Run(() => LongProcess()).ContinueWith( t => Run(() => LongerProcess()));

        int count = 5;
        do
        {
            Console.WriteLine(count.ToString());
            Thread.Sleep(100);
            count++;
        } while (count < 20);

        Console.ReadKey();
    }

    private static void LongProcess()
    {

        Console.WriteLine("Long Process 1");
        Thread.Sleep(2000);
        Console.WriteLine("Long Process 2");
    }

    private static void LongerProcess()
    {
        Console.WriteLine("Longer Process 1");
        Thread.Sleep(3000);
        Console.WriteLine("Longer Process 2");
    }

    private static async Task Run(Action action)
    {

        await Task.Run(action);  //shouldn't this wait until the action is completed??
                                // how should I modify the code so that the program waits until action is done before returning?

    }

With the await Task.Run(action);, I am expecting that the program will wait until whatever action is done, before going on to the next step. In other words, I am expecting outputs like:

Long Process 1
Long Process 2
Longer Process 1
Longer Process 2
5
6
7
8
...
19

However, the output is like

Long Process 1
5
6
7
8
...
19
Long Process 2
Longer Process 1
Longer Process 2

Questions:

  1. Why isn't await Task.Run waits until action is completed?
  2. How to change the code so that it gives the output that I want?
Graviton
  • 81,782
  • 146
  • 424
  • 602

1 Answers1

4

Using await does not wait, that is the whole point. If you want to wait, you use Task.Wait.

However, the execution of the async method will only resume after the awaited task is completed (it is a continuation). Let us see your async method:

private static async Task Run(Action action)
{

    await Task.Run(action);
    // There is nothing here
}

There is no code to execute after the task is done... Alright, the async method returns a task that completes when it is done. Let us see if you await that...

Run(() => LongProcess()).ContinueWith( t => Run(() => LongerProcess()));

You don't. You fire that task and forget about it.


I think this is what you want:

static async void Main(string[] args)
{
    await Run(() => LongProcess());
    await Run(() => LongerProcess());

    int count = 5;
    do
    {
        Console.WriteLine(count.ToString());
        Thread.Sleep(100);
        count++;
    } while (count < 20);

    Console.ReadKey();
}

Note: Async Main is a C# 7.1 feature.

This code will run the task that invokes LongProcess, and then as a continuation the task that runs LongerProcess, and then as a continuation the rest of the code.

Why would you want that instead of just calling these methods synchronously is beyond me.

By the way, in an async method you can use await Task.Delay(milliseconds) instead of Thread.Sleep(milliseconds). The advantage is that the thread is not waiting. It like making a continuation on a single execution timer.


If async/await is just continuations, you might be wondering why would people want to use them instead of ContinueWith.

Let me give you a few reasons:

  • Code with async/await is easier to read. Less nested stuff clouding the code. You can understand what the code does independently of understanding threading.
  • Code with async/await is easier to write. You just write it as if it were all synchronous, with some async and await sparkled in there. You can write synchronous code and then worry how to make it concurrent. You also end up writing less, which means you are more productive.
  • Code with async/await is smarter™. The compiler rewrites your code as a continuation state machine, allowing you to put await inside statements without any code gymnastics™. And this extends to exception handling. You see, in your fire and forget task continuation, you are not handling exceptions. What if LongerProcess throws? Well, with async/await you can just put the await inside of a try ... catch and it does the right thing™.

That is all good separation of concerns.


You may also wonder in what situation it is a good idea to use async/await instead of synchronous code. And the answer is that it shines when dealing with I/O operations. When interacting with an external system it is common to not have a response right away. For example, if you want to read from disk or download from the network.

In fact, if you consider manipulating a form and handling its event to be I/O bound operations (because, they are). You will see that they are a good place to use async/await. In particular given that the synchronization context for UI threads will keep continuations on the same thread by default. You can, of course, use async/await with CPU bound operations by using Task.Run. When is that a good idea? Ah, yes, when you want to keep the UI responsive.

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Thank you for your excellent explanation, one thing I'm not clear: " Less nested stuff clouding the code."-- what do you mean? Can illustrate why `ContinueWith` leads to more nesting? – Graviton Jan 24 '20 at 09:29
  • @Graviton I was thinking of nesting expressions. This `Run(() => LongProcess()).ContinueWith( t => Run(() => LongerProcess()));` already has more nesting than this `await Run(() => LongProcess()); await Run(() => LongerProcess());`. You could, of course, choose to make `LongProcess` and `LongerProcess` async, and then you have `await LongProcessAsync(); await LongerProcessAsync();`. – Theraot Jan 24 '20 at 10:16
  • @Graviton With that said, if you wanted to use `ContinueWith`, and inside the continuation you want to call some async methods... then you can have some task nesting which would be avoided by simply using `await`. See [C# Chained ContinueWith Not Waiting for Previous Task to Complete](https://stackoverflow.com/q/46235216/402022) for example. Notice that the not nested solution requires `Wait`, meaning that it has some thread wasted in waiting. While the `await` solution is easier to read and does the right thing™. – Theraot Jan 24 '20 at 10:19