ChatGPT解决这个技术问题 Extra ChatGPT

多个等待与 Task.WaitAll - 等效?

在性能方面,这两种方法会并行运行 GetAllWidgets()GetAllFoos() 吗?

有什么理由使用其中一个吗?编译器在幕后似乎发生了很多事情,所以我不清楚。

============= 方法A:使用多个等待======================

public async Task<IHttpActionResult> MethodA()
{
    var customer = new Customer();

    customer.Widgets = await _widgetService.GetAllWidgets();
    customer.Foos = await _fooService.GetAllFoos();

    return Ok(customer);
}

=============== MethodB:使用Task.WaitAll =====================

public async Task<IHttpActionResult> MethodB()
{
    var customer = new Customer();

    var getAllWidgetsTask = _widgetService.GetAllWidgets();
    var getAllFoosTask = _fooService.GetAllFos();

    Task.WaitAll(new List[] {getAllWidgetsTask, getAllFoosTask});

    customer.Widgets = getAllWidgetsTask.Result;
    customer.Foos = getAllFoosTask.Result;

    return Ok(customer);
}

======================================

在您的第一个示例中,这两个方法将按顺序调用,而在第二个示例中,它们将并行运行,因此它们不等效。此外,在您的第二种方法中,您在执行任务时会阻塞。
MethodA 将仅在 _widgetService.GetAllWidgets() 完成时执行 _fooService.GetAllFoos(),methodB 将在来自 _fooService.GetAllFoos() 的未完成任务返回时执行它。
@DanielKelley 第二个不保证它们会并行运行。正如在异步操作的其他答案和解释中经常强调的那样,它们不一定是多线程或并行的。有些人甚至坚持认为它们根本不是多线程的,但这是不必要的限制。根据实现的不同,它们可能会并行运行,但可以说最强的是它们不能保证按顺序运行。

i
i3arnon

第一个选项不会同时执行这两个操作。它将执行第一个并等待其完成,然后才执行第二个。

第二个选项将同时执行,但将同步等待它们(即在阻塞线程时)。

您不应该同时使用这两个选项,因为第一个比第二个完成得慢,而第二个在不需要的情况下阻塞线程。

您应该使用 Task.WhenAll 异步等待这两个操作:

public async Task<IHttpActionResult> MethodB()
{
    var customer = new Customer();

    var getAllWidgetsTask = _widgetService.GetAllWidgets();
    var getAllFoosTask = _fooService.GetAllFos();

    await Task.WhenAll(getAllWidgetsTask, getAllFoosTask);

    customer.Widgets = await getAllWidgetsTask;
    customer.Foos = await getAllFoosTask;

    return Ok(customer);
}

请注意,在 Task.WhenAll 完成后,两个任务都已完成,因此等待它们立即完成。


谢谢。这就是我需要的。 “.Result”困扰着我,你的回答避免了这种情况。
您也可以完全跳过 await Task.WhenAll(getAllWidgetsTask, getAllFoosTask); 并等待任务(在等待第一个任务之前启动第二个任务)。
@kwesoly 是的,但是仅“暂停”一次会更有效。
@i3arnon - 听起来是个好理由,真的没有必要为了再次等待而切换上下文。
@StephenHolt 两者之间只有一个区别。 Result 将所有异常包装在 AggregateException 中,然后您需要在所有错误处理代码中解包。 await 为您解开它。除此之外,它们是相同的。
N
Nitram

简短的回答:没有。

Task.WaitAll 是阻塞的,await 一遇到任务就返回,并注册函数的剩余部分和延续。

您正在寻找的“批量”等待方法是 Task.WhenAll,它实际上创建了一个新的 Task,它会在所有交给该函数的任务完成后完成。

像这样:await Task.WhenAll({getAllWidgetsTask, getAllFoosTask});

那是为了阻塞问题。

此外,您的第一个函数不会并行执行这两个函数。要使其与 await 一起使用,您必须编写如下内容:

var widgetsTask = _widgetService.GetAllWidgets();
var foosTask = _fooService.GetAllWidgets();
customer.Widgets = await widgetsTask;
customer.Foos = await foosTask;

这将使第一个示例的行为与 Task.WhenAll 方法非常相似。


n
nvoigt

只有您的第二个选项将并行运行它们。您的第一个将按顺序等待每个呼叫。


K
Kasper Holdum

一旦您调用异步方法,它将开始执行。无法确定它是在当前线程上执行(因此同步运行)还是异步运行。

因此,在您的第一个示例中,第一个方法将开始工作,但随后您使用 await 人为地停止了代码流。因此,在第一个方法完成执行之前,不会调用第二个方法。

第二个示例调用这两种方法,而不用等待来停止流。因此,如果方法是异步的,它们可能会并行运行。


p
pnizzle

作为@i3arnon 所说的补充。您会看到,当您使用 await 时,您必须将封闭方法声明为 async,但使用 waitAll 您不需要。这应该告诉您,它比主要答案所说的要多。这里是:

WaitAll 将阻塞直到给定任务完成,当这些任务正在运行时,它不会将控制权交还给调用者。同样如前所述,任务与它们自己异步运行,而不是与调用者异步运行。

Await 不会阻塞调用者线程,但它会暂停其下方代码的执行,但在任务运行时,控制权会返回给调用者。对于将控制权返回给调用者(被调用的方法正在运行异步)这一事实,您必须将该方法标记为异步。

希望区别很明显。干杯