ChatGPT解决这个技术问题 Extra ChatGPT

异步 CTP 中的 Task.WaitAll()Task.WhenAll() 有什么区别?您能否提供一些示例代码来说明不同的用例?


A
Alberto Solano

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

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

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

await Task.WhenAll(tasks);

...这意味着当一切都完成后,您的方法将继续,但您不会占用线程来闲逛直到那个时候。


经过大量阅读,很明显异步与线程无关blog.stephencleary.com/2013/11/there-is-no-thread.html
@Vince:我认为“与线程无关”是夸大其词,了解异步操作如何与线程交互很重要。
@KevinBui:不,它不应该 block 它 - 它会 await WhenAll 返回的任务,但这与阻塞线程不同。
@JonSkeet 也许这两者之间的精确区别对我来说太微妙了。您能否向我(可能还有我们其他人)指出一些可以明确区别的参考资料?
@CatShoes:不是真的-我已经尽可能地解释了。我想我可以打个比方——这就像订购外卖然后站在门口等待它到达与订购外卖,做其他事情然后在快递到达时打开门之间的区别......
t
tymtam

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

感谢您指出了这一点。这个解释在我目前正在工作的场景中很有用。也许不是“最大的实际差异”,但绝对是一个很好的呼吁。
异常处理是最大的实际差异可能更适用于 await t1; await t2; await t3;await Task.WhenAll(t1,t2,t3); 之间的比较
这种异常行为是否与此处的文档相矛盾(docs.microsoft.com/en-us/dotnet/api/…)“如果提供的任何任务在故障状态下完成,则返回的任务也将在故障状态下完成,其中它的异常将包含集合的聚合每个提供的任务的解包异常。”
我认为这是 await 的产物,而不是两种方法之间的区别。两者都传播 AggregateException,直接抛出或通过属性(Task.Exception 属性)。
L
Liam

作为差异的一个示例 - 如果您有一个任务与 UI 线程(例如,代表故事板中的动画的任务)如果您 Task.WaitAll() 则 UI 线程被阻止并且 UI 永远不会更新。如果您使用 await Task.WhenAll(),则 UI 线程不会被阻塞,并且 UI 将被更新。


如果您设置 ConfigureAwait(false); 则可以避免这种情况。关于等待的任务。不过,我不会推荐使用 WaitAll
i
i100

他们在做什么:

在内部,两者都做同样的事情。

有什么不同:

WaitAll 是一个阻塞调用

WhenAll - not - 代码将继续执行

在以下情况下使用 which:

WaitAll 在没有结果的情况下无法继续

WhenAll 什么时候只通知什么,而不是阻塞


@MartinRhodes 但是,如果您不立即等待它,而是继续进行其他一些工作并然后等待它怎么办?据我了解,WaitAll 没有这种可能性。
@Jeppe 您难道不会在您完成其他工作之后Task.WaitAll 改变对Task.WaitAll 的调用吗?我的意思是,而不是在开始任务后立即调用它。
J
Jeremy Caney

Task.WaitAll 阻塞当前线程。在所有其他任务完成执行之前,它将保持阻塞状态。它有一个 void 返回值。 Task.WhenAll 方法返回一个 Task<TResult[]>。它用于创建当且仅当所有其他任务都已完成时才会完成的任务。

什么时候用哪个?

大约我唯一一次使用 Task.WaitAll 是在我想添加并发性的非 async 函数(必须保持非 async)内。但是,请注意:这可能会导致死锁,因为它会阻塞当前线程。

考虑到这一点,您可以随时将函数转换为 async,执行此操作,然后使用 Task.WhenAll,其上带有 await。这绝对是首选方法。

例外

当任何任务抛出时,Task.WaitAll 会抛出 AggregateException,您可以检查所有抛出的异常。 await Task.WhenAll 中的 await 解开 AggregateException 并仅“返回”第一个异常。在这两种情况下,所有任务都将运行,即使其中一个引发异常。