36

Ideally what I want to do is to delay a task with a non-blocking mode and then wait for all the tasks to complete. I've tried to add the task object returned by Task.Delay and then use Task.WaitAll but seems this won't help. How should I solve this problem?

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

        var newTask = Task.Delay(1000);
        TaskList.Add(newTask);
        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;
            TaskList.Add(Task.Factory.StartNew(() => Foo(idx)));
        }

        Task.WaitAll(TaskList.ToArray());
    }
}
derekhh
  • 5,272
  • 11
  • 40
  • 60
  • Why are you doing this in a Console Application? There's no context (by default), so `await` tends to behave strangely anyways... – Reed Copsey Nov 08 '13 at 01:14
  • What exactly are you expecting to see in the console? How does this differ from what you are seeing? – Andrew Shepherd Nov 08 '13 at 01:23
  • @AndrewShepherd: What I expect to see are six lines showing both Start and End of each task. But right now I'm seeing only Start... – derekhh Nov 08 '13 at 01:24
  • Your code seems to work for me ... (running in a wpf solution, but still, the code works once the end runs on the GUI thread...) – Noctis Nov 08 '13 at 01:37
  • 7
    `async void` method that you pass to `Task.Factory.StartNew` is a bad idea. You cannot keep track of the pending task it starts or catch any exceptions it may throw after `await`. Check this for more info: http://stackoverflow.com/q/19747910/1768303 – noseratio Nov 08 '13 at 01:58

2 Answers2

53

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...
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Not really. This would only output the first three "Start ..." lines and won't output "End ...". It means these tasks haven't really completed (well, I understand new task objects are created instead of the original ones added to the tasklist). So I'd really want to find a way that can correctly behave and show that these three tasks are completed. – derekhh Nov 08 '13 at 01:20
  • @derekhh, have you tried it? I've posted a complete example. You see Start/Start/Start, because you start tasks in parallel. There is matching End for each tasks. Or do you want to start them one after another? – noseratio Nov 08 '13 at 01:30
  • 3
    Thanks! I've just noticed that you've also changed the return type of Foo from void to Task. Yes, changing this back would exactly see the same behavior as you wrote. :) – derekhh Nov 08 '13 at 02:09
  • @derekhh, yep, see my comments to your question about `async void`. Also, make sure to read [Stephen Toub's blog post](http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx) about nested tasks, it's very informative. – noseratio Nov 08 '13 at 02:12
32

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.

Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • 2
    You create nested tasks here, which `Task.Run` automatically unwraps, which is unnecessary, IMO. Otherwise, the code seems to be no different from the [version I proposed](http://stackoverflow.com/a/19849906/1768303). Not sure though it's what the OP wants. – noseratio Nov 08 '13 at 01:59