WaitAll vs WhenAll


Answers:


503

Task.WaitAll 阻塞当前线程,直到一切完成。

Task.WhenAll返回一个任务,表示等待一切完成的动作。

这意味着从异步方法中,您可以使用:

await Task.WhenAll(tasks);

...这意味着您的方法将在完成所有操作后继续,但是您不会在此之前一直绑一个线程。


2
经过大量阅读后,很明显,异步与线程无关。blog.stephencleary.com/2013
文斯·

7
@Vince:我认为“与线程无关”是一种夸大的说法,了解异步操作如何与线程交互非常重要。
乔恩·斯基特

6
@KevinBui:不,它不应该阻止它-它将等待所返回的任务WhenAll,但这与阻止线程不同。
乔恩·斯基特

1
@JonSkeet也许这两者之间的精确区分对我来说太微妙了。您能为我(可能还有我们其他人)提供一些可以使两者有所区别的参考吗?
CatShoes

125
@CatShoes:并非如此-我已经尽力解释了。我想我可以提供一个类比-就像订购外卖品然后站在门口等待外卖与订购外卖品,做其他事情然后在快递员到达时打开门之间的区别……
乔恩Skeet 2015年

50

尽管JonSkeet的回答以一种典型的出色方式解释了这种差异,但还有另一个差异:异常处理

Task.WaitAllAggregateException当任何任务抛出时,您将抛出一个,您可以检查所有抛出的异常。将awaitawait Task.WhenAll解包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
实际最大的不同是异常处理。真?因为那实际上不是最大的实际差异。实际的最大区别是,一个是异步和非阻塞,而另一个是阻塞。这比处理异常重要得多。
利亚姆

5
感谢您指出了这一点。该解释在我当前正在工作的场景中很有用。也许不是“最大的实际差异”,但绝对是一个不错的选择。
Urk,

与实际操作最大的不同是,异常处理可能更适用于await t1; await t2; await t3;vsawait Task.WhenAll(t1,t2,t3);
frostshoxx

1
此异常行为是否与此处的文档(docs.microsoft.com/en-us/dotnet/api/…)相矛盾?“如果提供的任何任务以故障状态完成,则返回的任务也将以故障状态完成,其异常将包含来自每个提供的任务的未包装异常的集合的聚合。”
Dasith Wijes

1
我认为这是的产物await,不是这两种方法之间的区别。两者都传播AggregateException,可以直接抛出,也可以通过一个属性(该Task.Exception属性)抛出。
西奥多·祖里亚斯

20

区别的一个例子-如果您有一个任务,则如果您有一个任务,它将使用UI线程(例如,在情节提要中代表动画的任务)执行某些操作,Task.WaitAll()然后UI线程将被阻塞并且UI永远不会更新。如果使用,await Task.WhenAll()则不会阻塞UI线程,并且将更新UI。


7

他们在做什么:

  • 内部都做同样的事情。

有什么不同:

  • WaitAll是一个阻止呼叫
  • WhenAll-不-代码将继续执行

在以下情况下使用:

  • 没有结果就无法继续的WaitAll
  • WhenAll什么时候才被通知,不会被阻止

1
@MartinRhodes但是,如果您不立即等待它,而是继续进行其他工作然后再等待,该怎么办?WaitAll据我了解,您没有这种可能性。
杰普

@Jeppe 完成其他工作Task.WaitAll 后,您难道不就将电话更改为吗?我的意思是,与其在启动任务后立即调用它,不如说它是在。
PL
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.