Casey Crookston
Casey Crookston

Reputation: 13955

async & await - How to wait until all Tasks are done?

ok. I made a simple console app to figure out how to make all this work. Once I have the basic outline working, then I'll apply it to the real application.

The idea is that we have a lot of database calls to execute that we know are going to take a long time. We do NOT want to (or have to) wait for one database call to be completed before we make the next. They can all run at the same time.

But, before making all of the calls, we need to perform a "starting" task. And when all of the calls are complete, we need to perform a "finished" task.

Here's where I'm at now:

static void Main(string[] args)
{
    Console.WriteLine("starting");
    PrintAsync().Wait();        
    Console.WriteLine("ending");  // Must not fire until all tasks are finished
    Console.Read();
}

// Missing an "await", I know. But what do I await for?
static async Task PrintAsync()
{
    Task.Run(() => PrintOne());
    Task.Run(() => PrintTwo());
}

static void PrintOne()
{
    Console.WriteLine("one - start");
    Thread.Sleep(3000);
    Console.WriteLine("one - finish");
}

static void PrintTwo()
{
    Console.WriteLine("two - start");
    Thread.Sleep(3000);
    Console.WriteLine("two - finish");
}

But no matter what I try, Ending always gets printed too early:

starting
ending
one - start
two - start
one - finish
two - finish

What IS working right is that PrintTwo() starts before PrintOne() is done. But how do I properly wait for PrintAsync() to finish before doing anything else?

Upvotes: 4

Views: 3698

Answers (2)

Casey Crookston
Casey Crookston

Reputation: 13955

OP here. I'm going to leave the answer by Mong Zhu marked as correct, as it lead me to the solution. But I also want to share the final result here, which includes excellent feedback in the comments from juharr. Here's what I came up with:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("starting");
        DatabaseCallsAsync().Wait();            
        Console.WriteLine("ending"); // Must not fire until all database calls are complete.
        Console.Read();
    }

    static async Task DatabaseCallsAsync()
    {
        // This is one way to do it...
        var tasks = new List<Task>();
        for (int i = 0; i < 3; i++)
        {
            tasks.Add(DatabaseCallAsync($"Task {i}"));
        }
        await Task.WhenAll(tasks.ToArray());

        // This is another.  Same result...
        List<int> inputParameters = new List<int> { 1, 2, 3, 4, 5 };
        await Task.WhenAll(inputParameters.Select(x => DatabaseCallAsync($"Task {x}")));
    }

    static async Task DatabaseCallAsync(string taskName)
    {
        Console.WriteLine($"{taskName}: start");
        await Task.Delay(3000);
        Console.WriteLine($"{taskName}: finish");
    }
}

Here's the result:

starting
Task 0: start
Task 1: start
Task 2: start
Task 2: finish
Task 0: finish
Task 1: finish
ending

Upvotes: 0

Mong Zhu
Mong Zhu

Reputation: 23732

you need to await the ending of the inner tasks:

static async Task PrintAsync()
{
    await Task.WhenAll(Task.Run(() => PrintOne()), Task.Run(() => PrintTwo()));
}

explanation: async Task denotes an awaitable method. Inside this method you can also await Tasks. If you don't do this then it will simply let the tasks loose which will run on their own. Task.Run returns a Task which can be awaited. If you want both tasks to run in parallel you can use the tasks from the retur values and use them in the awaitable method Task.WhenAll

EDIT: Actually Visual Studio would mark this code with a green curvy line. When hoovering with the mouse over it you get a warning:

enter image description here

CS4014

This should explain why "ending" is printed before the tasks have finished

EDIT 2:

If you have a collection of parameters that you want to iterate and call an async method to pass the parameter in, you can also do it with a select statement in 1 line:

static async Task DatabaseCallsAsync()
{
    // List of input parameters
    List<int> inputParameters = new List<int> {1,2,3,4,5};  
    await Task.WhenAll(inputParameters.Select(x => DatabaseCallAsync($"Task {x}")));
}

static async Task DatabaseCallAsync(string taskName)
{
    Console.WriteLine($"{taskName}: start");
    await Task.Delay(3000);
    Console.WriteLine($"{taskName}: finish");
}

The last part is similar to a previous answer

Upvotes: 8

Related Questions