Use Task.WaitAll() to handle awaited tasks?

Is this what you are trying to achieve?

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    class Program
    {
        public static async Task Foo(int num)
        {
            Console.WriteLine("Thread {0} - Start {1}", Thread.CurrentThread.ManagedThreadId, num);

            await Task.Delay(1000);

            Console.WriteLine("Thread {0} - End {1}", Thread.CurrentThread.ManagedThreadId, num);
        }

        public static List<Task> TaskList = new List<Task>();

        public static void Main(string[] args)
        {
            for (int i = 0; i < 3; i++)
            {
                int idx = i;
                TaskList.Add(Foo(idx));
            }

            Task.WaitAll(TaskList.ToArray());
            Console.WriteLine("Press Enter to exit...");
            Console.ReadLine();
        }
    }
}

Output:

Thread 10 - Start 0
Thread 10 - Start 1
Thread 10 - Start 2
Thread 6 - End 0
Thread 6 - End 2
Thread 6 - End 1
Press Enter to exit...

The thing to be aware of is that because Foo is async, it itself is a Task. Your example has tasks which simply kick off the Foo task, but don't wait for it.

In other words, Task.WaitAll(TaskList.ToArray()) is simply waiting for each Task.Delay to start, but it is not waiting for all of these tasks to finish.

This might be what you are trying to achieve:

class Program
{
    public static async Task Foo(int num)
    {
        Console.WriteLine("Thread {0} - Start {1}", Thread.CurrentThread.ManagedThreadId, num);

        var newTask = Task.Delay(1000);

        await newTask;
        Console.WriteLine("Thread {0} - End {1}", Thread.CurrentThread.ManagedThreadId, num);

    }

    public static List<Task> TaskList = new List<Task>();

    public static void Main(string[] args)
    {
        for (int i = 0; i < 3; i++)
        {
            int idx = i;

            Task fooWrappedInTask = Task.Run(() => Foo(idx));
            TaskList.Add(fooWrappedInTask);
        }

        Task.WaitAll(TaskList.ToArray());
        Console.WriteLine("Finished waiting for all of the tasks: - Thread {0}", Thread.CurrentThread.ManagedThreadId);
    }
}

I've tested this, and it produces the console output you are aiming for.


The principal difference here is that we're calling Task.Run instead of Task.Factory.StartNew.

You might have a Task that returns a Task, which might even return another Task. You would think of this as a 'chain' of tasks.

Task.Run returns a Task that represent the final task in the chain. When you wait for it, you are waiting for the every link in the chain of tasks to complete.

In comparison, Task.Factory.StartNew returns a task that represents the first link in the chain. After you have waited for it, you are left with the rest of the chain to wait for. This is fine in the cases where the Task returns something that isn't another Task.