使用Task.WaitAll()处理等待的任务?

37

我想要做的是以非阻塞模式延迟任务,然后等待所有任务完成。我尝试添加通过Task.Delay返回的任务对象,然后使用Task.WaitAll,但似乎这样做没用。我应该如何解决这个问题?

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());
    }
}

你为什么要在控制台应用程序中这样做?默认情况下没有上下文,所以await的行为通常会变得奇怪... - Reed Copsey
你期望在控制台中看到什么?这与你当前看到的有何不同之处? - Andrew Shepherd
@AndrewShepherd:我期望看到的是六行,显示每个任务的开始和结束时间。但现在我只能看到开始时间... - derekhh
你的代码在我的电脑上似乎可以正常运行...(虽然是在WPF解决方案中运行,但一旦结束在GUI线程上运行,代码就可以正常工作...) - Noctis
7
async void方法传递给Task.Factory.StartNew是不好的做法。在await之后,您无法跟踪所启动的挂起任务或捕获其可能抛出的任何异常。请参阅此链接了解更多信息:https://dev59.com/nnjZa4cB1Zd3GeqPhLEB - noseratio - open to work
2个回答

53

这是你想要达到的目标吗?

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();
        }
    }
}

输出:

线程10 - 开始0
线程10 - 开始1
线程10 - 开始2
线程6 - 结束0
线程6 - 结束2
线程6 - 结束1
按Enter键退出...

不完全是这样。这只会输出前三个“开始…”行,而不会输出“结束…”。这意味着这些任务并没有真正完成(我理解新的任务对象被创建,而不是添加到任务列表中的原始对象)。因此,我真的希望找到一种可以正确表现和显示这三个任务已经完成的方法。 - derekhh
@derekhh,你试过了吗?我已经发布了一个完整的例子。你看到了Start/Start/Start,因为你在并行启动任务。每个任务都有相应的End。或者你想一个接一个地启动它们? - noseratio - open to work
3
谢谢!我刚刚注意到你把Foo的返回类型从void改成了Task。是的,如果改回来,它会与你写的代码产生完全相同的行为。:) - derekhh
@derekhh,是的,请看看我对你关于“async void”的问题的评论。另外,请务必阅读Stephen Toub的博客文章关于嵌套任务的内容,非常有启发性。 - noseratio - open to work

32

需要注意的是,由于Foo是异步的,它本身也是一个任务。您的示例有一些仅启动Foo任务但不等待其完成的任务。

换句话说,Task.WaitAll(TaskList.ToArray())仅等待每个Task.Delay开始,但它不会等待所有这些任务完成。

这可能是您正在尝试实现的内容:

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);
    }
}

我已经测试过了,它产生了您想要的控制台输出。


这里的主要区别在于我们调用了Task.Run而不是Task.Factory.StartNew

您可能会有一个返回TaskTask,甚至可能返回另一个Task。您可以将此视为任务的“链”。

Task.Run返回代表链中最终任务的Task。当您等待它时,您正在等待任务链中的每个链接完成。

相比之下,Task.Factory.StartNew返回表示链中第一个链接的任务。等待它后,您还需要等待其他部分的任务链。如果任务返回的不是另一个Task,则这很好。


2
你在这里创建了嵌套任务,而 Task.Run 会自动解包它们,这是不必要的,我认为。除此之外,代码似乎与 我提出的版本 没有什么区别。不过我不确定这是否是 OP 想要的。 - noseratio - open to work

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接