代码之家  ›  专栏  ›  技术社区  ›  Theodor Zoulias

为什么任务的延续。何时所有操作都同步执行?

  •  1
  • Theodor Zoulias  · 技术社区  · 4 年前

    我刚刚对 Task.WhenAll 方法,在运行时。NET Core 3.0。我通过了一个简单的 Task.Delay 任务作为一个单一的论点 任务。When All ,我希望包装好的任务的行为与原始任务相同。但事实并非如此。原始任务的延续是异步执行的(这是可取的),多个任务的延续 Task.WhenAll(task) 包装器一个接一个地同步执行(这是不理想的)。

    这是一个 demo 这种行为。四个工作任务正在等待相同的任务 任务。延误 完成任务,然后继续进行繁重的计算(由 Thread.Sleep ).

    var task = Task.Delay(500);
    var workers = Enumerable.Range(1, 4).Select(async x =>
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
            $" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} before await");
    
        await task;
        //await Task.WhenAll(task);
    
        Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
            $" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} after await");
    
        Thread.Sleep(1000); // Simulate some heavy CPU-bound computation
    }).ToArray();
    Task.WaitAll(workers);
    

    这是输出。这四个延续按预期在不同线程中并行运行。

    05:23:25.511 [1] Worker1 before await
    05:23:25.542 [1] Worker2 before await
    05:23:25.543 [1] Worker3 before await
    05:23:25.543 [1] Worker4 before await
    05:23:25.610 [4] Worker1 after await
    05:23:25.610 [7] Worker2 after await
    05:23:25.610 [6] Worker3 after await
    05:23:25.610 [5] Worker4 after await
    

    现在,如果我评论这句话 await task 并取消对以下行的注释 await Task.WhenAll(task) ,输出完全不同。所有延续都在同一个线程中运行,因此计算不是并行的。每次计算都是在前一次计算完成后开始的:

    05:23:46.550 [1] Worker1 before await
    05:23:46.575 [1] Worker2 before await
    05:23:46.576 [1] Worker3 before await
    05:23:46.576 [1] Worker4 before await
    05:23:46.645 [4] Worker1 after await
    05:23:47.648 [4] Worker2 after await
    05:23:48.650 [4] Worker3 after await
    05:23:49.651 [4] Worker4 after await
    

    令人惊讶的是,只有当每个工人等待不同的包装器时,才会发生这种情况。如果我预先定义包装器:

    var task = Task.WhenAll(Task.Delay(500));
    

    …然后 await 在所有worker中执行相同的任务时,行为与第一种情况(异步延续)相同。

    我的问题是: 为什么会发生这种情况?是什么导致同一任务的不同包装器的延续在同一线程中同步执行?

    注: 用以下命令包装任务 Task.WhenAny 而不是 任务。When All 导致同样的奇怪行为。

    另一个观察: 我原以为把包装纸包在里面 Task.Run 将使延续异步。但这并没有发生。以下行的延续仍然在同一线程中执行(同步)。

    await Task.Run(async () => await Task.WhenAll(task));
    

    澄清: 在上运行的Console应用程序中观察到上述差异。NET Core 3.0平台。上。NET Framework 4.8中,等待原始任务或任务包装器之间没有区别。在这两种情况下,延续都是在同一线程中同步执行的。

    0 回复  |  直到 4 年前
        1
  •  4
  •   Jeremy Lakeman    4 年前

    因此,您有多个异步方法等待同一个任务变量;

        await task;
        // CPU heavy operation
    

    是的,当以下情况发生时,这些延续将被连续调用 task 完成。在您的示例中,每个延续都会在下一秒占用线程。

    如果你想让每个延续异步运行,你可能需要这样的东西:;

        await task;
        await Task.Yield().ConfigureAwait(false);
        // CPU heavy operation
    

    这样你的任务就可以从最初的延续中返回,并允许CPU负载在外部运行 SynchronizationContext .

        2
  •  1
  •   Tanveer Badar    4 年前

    当使用创建任务时 Task.Delay() ,其创建选项设置为 None 而不是 RunContinuationsAsychronously .

    这可能会破坏.net框架和.net核心之间的变化。不管怎样,它似乎确实解释了你观察到的行为。您还可以通过深入挖掘源代码来验证这一点 任务。延迟() newing up DelayPromise 这将调用默认值 Task 构造函数未指定创建选项。

        3
  •  0
  •   Seyedraouf Modarresi    4 年前

    在您的代码中,以下代码不在重复体中。

    var task = Task.Delay(100);
    

    因此,每次运行以下命令时,它都会等待任务并在单独的线程中运行它

    await task;
    

    但如果运行以下命令,它将检查 task ,因此它将在一个线程中运行它

    await Task.WhenAll(task);
    

    但如果将任务创建移到旁边 WhenAll 它将在单独的线程中运行每个任务。

    var task = Task.Delay(100);
    await Task.WhenAll(task);
    
    推荐文章