【问题标题】:Difference between await Task and await Task.WhenAllawait Task 和 await Task.WhenAll 的区别
【发布时间】:2017-03-04 06:24:02
【问题描述】:

在多个等待任务上使用await 与等待所有任务完成有什么区别。我的理解是场景 2 在性能方面更好,因为 asyncTask1 和 asyncTask2 都是并行执行的。

场景 1:

async Task Task()
{
   await asyncTask1();
   await asyncTask2();
}

场景 2:

async Task Task()
{
    t1 = asyncTask1();
    t2 =  asyncTask2();
    await Task.WhenAll(createWorkflowtask, getTaskWorkflowTask);
}

【问题讨论】:

  • 鉴于您自己已经描述了不同之处,您为什么要问我们有什么不同?你已经知道了。
  • @servry ,我认为这是会带来更好性能的场景,但不确定它是否涵盖所有边缘情况。或者如果有短期运行任务的场景。使用并行方法不是最好的选择。
  • 性能影响将取决于大量因素。两段代码的区别在于,前者顺序执行操作,后者并行执行。

标签: c# asynchronous async-await task-parallel-library


【解决方案1】:

在场景 1 中,任务按顺序运行(asyncTask1 必须在 asyncTask2 启动之前完成),而在场景 2 中,两个任务可以并行运行。

【讨论】:

    【解决方案2】:

    在第一个场景中,您启动一​​个任务,然后等到完成,然后传递给第二个,等到完成后再退出该方法。
    在第二种情况下,您并行启动两个任务,然后在调用 Task.WhenAll 时等到 tgey 完成

    【讨论】:

      【解决方案3】:

      使用Task.WhenAll

      • 除非所有返回类型都相同并且使用WhenAll 方法的泛型版本,否则您不能使用具有返回类型的方法
      • 您无法控制方法执行的顺序,因为这些方法是并行执行的
      • 一种方法中未处理的异常不会中断其他方法的执行(因为并行性质)

      来自MSDN

      如果提供的任何任务在故障状态下完成,则返回的任务也将在 TaskStatus.Faulted 状态下完成,其异常将包含来自每个提供的任务的未包装异常集的聚合。

      在多种方法上使用await

      • 您可以控制函数调用的序列

      • 您可以使用不同的返回类型并在后续步骤中使用该返回类型

      • 一种方法中未处理的异常会中断其他方法的执行

      【讨论】:

        【解决方案4】:

        您写道:“...因为 asyncTask1 和 asyncTask2 都是并行执行的。”

        不,他们不是!

        补充:下面我写了 async-await 中的一切都是由一个线程执行的。 Schneider 正确地评论说,在 async-await 中可以涉及多个线程。见最后的补充。

        this interview with Eric-Lippert 将 async/await 与做饭的厨师进行了比较,这对我了解 async-await 的工作原理有很大帮助。 (中途某处,搜索 async-await)。

        Eric Lippert 解释说,如果厨师开始做某事并在一段时间后发现他无事可做,只能等待一个过程完成,那么这位厨师会环顾四周,看看他是否可以做其他事情而不是等待。

        使用 async/await 时,仍然涉及一个线程。这个线程一次只能做一件事。当线程忙于执行Task1 时,它无法执行Task2。只有在 Task1 中找到 await 时,才会开始执行 Task2 中的语句。在您的场景 2 中,任务不是并行执行的。

        但是,场景之间存在差异。在场景 1 中,task2 的第一条语句在 task1 完全完成之前不会执行。场景 2 将在 task1 遇到 await 时立即开始执行 task2 的第一条语句。

        如果您真的希望 task2 在 task1 也在做某事的同时做某事,那么您必须在单独的线程中开始执行 task2。在您的场景中执行此操作的简单方法是:

        var task1 = Task.Run( () => asyncTask1())
        // this statement is executed while task1 begins executing on a different thread.
        // hence this thread is free to do other things, like performing statements
        // from task2:
        var task2 = asyncTask();
        // the following statement will only be executed if task2 encounters an await
        DoSomethingElse();
        // when we need results from both task1 and task2:
        await Task.WhenAll(new Task[] {task1, task2});
        

        所以通常情况下,如果您需要该任务的结果,最好只等待任务完成。只要你能做其他事情,做这些其他事情,它们会在其他任务开始等待时立即执行,直到你开始等待,在这种情况下,你的调用者将开始做事情,直到他的等待等。

        上述并行处理方法的优点是多方面的:

        • 一切都由一个线程执行:不需要互斥锁,没有死锁、饥饿等的机会
        • 您的代码看起来很有顺序。将此与使用 Task.ContinueWith 和类似语句的代码进行比较
        • 启动单独的线程/从线程池运行线程没有开销

        补充:下面施耐德关于几个线程的评论是正确的

        一些测试表明,等待任务中当前线程的线程 ID 与调用线程的线程 ID 不同。

        对于异步等待的新手来说,重要的是要了解虽然涉及到各种线程,但异步等待并不意味着任务是并行执行的。如果您想要并行性,您必须特别说明该任务必须并行运行。

        似乎 Eric Lippert 类比中的厨师实际上是一个厨师团队,他们不断地环顾四周,看看他们是否可以帮助其他一些厨师,而不是等待他们的任务完成。事实上,如果厨师 Albert 看到等待并开始做其他事情,厨师 Bernard 可能会完成厨师 Albert 的任务。

        【讨论】:

        • “当使用 async/await 时,仍然涉及一个线程......” - 我认为这一段具有误导性和/或混淆性。在许多情况下,可能会涉及多个线程。其中之一是执行的逻辑流程,不要与一个线程混淆。 “当线程忙于执行Task1时,它无法执行Task2。” - 这不是 Task2 不与 Task1 同时运行的原因 - 这是因为代码的逻辑流程决定了它。 Task2 在 Task1 之后的不可见延续中运行,可能在不同的线程上运行。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-02-12
        • 2019-10-08
        • 2013-09-04
        • 2022-11-15
        • 2013-06-04
        • 2017-02-12
        相关资源
        最近更新 更多