WaitAll与WhenAll的区别

509

Task.WaitAll()Task.WhenAll()这两个方法有什么区别?它们是Async CTP中的异步操作。您能提供一些示例代码来说明它们的不同用法吗?

4个回答

737

Task.WaitAll 阻塞当前线程直至所有任务完成。

Task.WhenAll 返回一个表征等待所有任务完成的任务

也就是说,在异步方法中,你可以使用:

await Task.WhenAll(tasks);

这意味着您的方法将在所有任务完成后继续执行,但您不需要绑定一个线程一直等到那个时候。


5
经过大量阅读,明确了异步与线程无关。http://blog.stephencleary.com/2013/11/there-is-no-thread.html - Razor
26
@Vince:我认为“与线程无关”这一说法有些过于绝对了,了解异步操作如何与线程交互非常重要。 - Jon Skeet
11
不,它不应该阻塞它-它将等待WhenAll返回的任务,但这与阻塞线程不同。 - Jon Skeet
4
@JonSkeet:也许这两者之间的精确区别对我来说太微妙了。你能指向一些参考资料,以便让我(和可能是我们其他人)明确这种差异吗? - CatShoes
204
@CatShoes:并不是,我已经尽可能清楚地解释了。我想我可以给出一个类比——就像在外卖点餐后,你可以站在门口等待送货上门,也可以在点餐后继续做其他事情,等到快递员到了再去开门... - Jon Skeet
显示剩余5条评论

129
虽然JonSkeet的回答已经很好地解释了区别,但还有另一个区别:异常处理。当任何一个任务抛出异常时,Task.WaitAll会抛出AggregateException,您可以检查所有抛出的异常。而在await Task.WhenAll中,await会展开AggregateException并且仅“返回”第一个异常。当下面的程序使用await Task.WhenAll(taskArray)执行时,输出如下。
19/11/2016 12:18:37 AM: Task 1 started
19/11/2016 12:18:37 AM: Task 3 started
19/11/2016 12:18:37 AM: Task 2 started
Caught Exception in Main at 19/11/2016 12:18:40 AM: Task 1 throwing at 19/11/2016 12:18:38 AM
Done.

当使用Task.WaitAll(taskArray)执行下面的程序时,输出结果如下。
19/11/2016 12:19:29 AM: Task 1 started
19/11/2016 12:19:29 AM: Task 2 started
19/11/2016 12:19:29 AM: Task 3 started
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 1 throwing at 19/11/2016 12:19:30 AM
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 2 throwing at 19/11/2016 12:19:31 AM
Caught AggregateException in Main at 19/11/2016 12:19:32 AM: Task 3 throwing at 19/11/2016 12:19:32 AM
Done.

该程序:
class MyAmazingProgram
{
    public class CustomException : Exception
    {
        public CustomException(String message) : base(message)
        { }
    }

    static void WaitAndThrow(int id, int waitInMs)
    {
        Console.WriteLine($"{DateTime.UtcNow}: Task {id} started");

        Thread.Sleep(waitInMs);
        throw new CustomException($"Task {id} throwing at {DateTime.UtcNow}");
    }

    static void Main(string[] args)
    {
        Task.Run(async () =>
        {
            await MyAmazingMethodAsync();
        }).Wait();

    }

    static async Task MyAmazingMethodAsync()
    {
        try
        {
            Task[] taskArray = { Task.Factory.StartNew(() => WaitAndThrow(1, 1000)),
                                 Task.Factory.StartNew(() => WaitAndThrow(2, 2000)),
                                 Task.Factory.StartNew(() => WaitAndThrow(3, 3000)) };

            Task.WaitAll(taskArray);
            //await Task.WhenAll(taskArray);
            Console.WriteLine("This isn't going to happen");
        }
        catch (AggregateException ex)
        {
            foreach (var inner in ex.InnerExceptions)
            {
                Console.WriteLine($"Caught AggregateException in Main at {DateTime.UtcNow}: " + inner.Message);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Caught Exception in Main at {DateTime.UtcNow}: " + ex.Message);
        }
        Console.WriteLine("Done.");
        Console.ReadLine();
    }
}

13
谢谢您指出这一点。对于我目前正在处理的情况,这个解释很有用。或许这不是“最大的实际区别”,但绝对值得关注。 - Urk
异常处理是最大的实际差异,可能更适用于await t1; await t2; await t3;await Task.WhenAll(t1,t2,t3);之间的比较。 - frostshoxx
2
这个异常行为是否与此处的文档(https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=netframework-4.7.2#System_Threading_Tasks_Task_WhenAll__1_System_Collections_Generic_IEnumerable_System_Threading_Tasks_Task___0___)相矛盾? “如果提供的任何任务以故障状态完成,则返回的任务也将以故障状态完成,其中其异常将包含来自每个提供的任务的一组未包装异常的聚合。” - Dasith Wijes
3
我认为这是 await 的产物,而不是这两种方法之间的差异。无论是直接抛出还是通过属性(Task.Exception 属性)传递,两种方法都会传播 AggregateException - Theodor Zoulias

42

举个例子来说明区别--
如果你有一个处理UI线程的任务(例如代表Storyboard中的动画的任务),如果你使用Task.WaitAll(),那么UI线程就会被阻塞,UI也不会被更新。
如果你使用await Task.WhenAll(),那么UI线程不会被阻塞,UI将会被更新。


如果您在等待任务上设置了ConfigureAwait(false);,那么这可以避免出现这种情况。我不建议使用WaitAll。 - X.Otano

28

他们都做什么:

  • 在内部,它们都执行相同的操作。

有什么区别:

  • WaitAll 是一个阻塞调用
  • WhenAll - 不是 - 代码将继续执行

何时使用哪个:

  • WaitAll 当没有结果就无法继续时
  • WhenAll 当只需要通知而不是阻塞时

2
@MartinRhodes 但是如果您不立即等待它,而是继续进行其他工作,然后再等待它呢?据我所知,使用WaitAll就没有这种可能性。 - Jeppe
@Jeppe 你不是可以在做完其他工作后再调用 Task.WaitAll 吗?我的意思是,不要在启动任务后立即调用它。 - P-L

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