gpt4 book ai didi

c# - WhenAll 与 WaitAll 并行

转载 作者:行者123 更新时间:2023-11-30 19:25:39 25 4
gpt4 key购买 nike

我试图了解 WaitAllWhenAll 是如何工作的,但遇到了以下问题。有两种可能的方法可以从方法中获取结果:

  1. return Task.WhenAll(tasks).Result.SelectMany(r=> r);
  2. return tasks.Select(t => t.Result).SelectMany(r => r).ToArray();

如果我理解正确,第二种情况就像在 tasks 上调用 WaitAll 并在之后获取结果。

看起来第二种情况的性能要好得多。我知道 WhenAll 的正确用法是使用 await 关键字,但我仍然想知道为什么这些行的性能差异如此之大。

在分析了系统的流程后,我想我已经想出了如何在一个简单的测试应用程序中对问题建模(测试代码基于 I3arnon 答案):

    public static void Test()
{
var tasks = Enumerable.Range(1, 1000).Select(n => Task.Run(() => Compute(n)));

var baseTasks = new Task[100];
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
baseTasks[i] = Task.Run(() =>
{
tasks.Select(t => t.Result).SelectMany(r => r).ToList();
});

}
Task.WaitAll(baseTasks);
Console.WriteLine("Select - {0}", stopwatch.Elapsed);

baseTasks = new Task[100];
stopwatch.Restart();
for (int i = 0; i < 100; i++)
{
baseTasks[i] = Task.Run(() =>
{
Task.WhenAll(tasks).Result.SelectMany(result => result).ToList();
});

}
Task.WaitAll(baseTasks);
Console.WriteLine("Task.WhenAll - {0}", stopwatch.Elapsed);
}

看起来问题出在从其他任务开始任务(或在 Parallel 循环中)。在那种情况下,WhenAll 会导致程序性能更差。这是为什么?

最佳答案

您正在 Parallel.ForEach 循环中开始任务,您应该避免这种情况。 Paralle.ForEach 的全部要点是在可用的 CPU 内核上并行化许多小而密集的计算,并且开始任务不是密集计算。相反,如果任务池饱和,它会创建一个任务对象并将其存储在队列中,很快就会有 1000 个任务被启动。所以现在 Parallel.ForEach 与任务池竞争计算资源。

在第一个非常慢的循环中,调度似乎不是最优的,使用的 CPU 很少可能是因为 Parallel.ForEach 中的 Task.WhenAll。如果将 Parallel.ForEach 更改为普通的 for 循环,您将看到加速。

但是,如果您的代码真的像 Compute 函数一样简单,并且在迭代之间没有任何状态转移,您可以摆脱任务并简单地使用 Parallel.ForEach 来最大化性能:

Parallel.For(0, 100, (i, s) =>
{
Enumerable.Range(1, 1000).Select(n => Compute(n)).SelectMany(r => r).ToList();
});

至于为什么 Task.WhenAll 执行得更差你应该意识到这段代码

tasks.Select(t => t.Result).SelectMany(r => r).ToList();

不会并行运行任务。 ToList 基本上将迭代包装在 foreach 循环中,循环主体创建一个任务,然后等待任务完成,因为您检索了 Task。结果 属性。所以循环的每次迭代都会创建一个任务,然后等待它完成。 1000个任务一个接一个执行,处理任务的开销很小。这意味着您不需要执行我在上面建议的任务。

另一方面,代码

Task.WhenAll(tasks).Result.SelectMany(result => result).ToList();

将启动所有任务并尝试并发执行它们,因为任务池无法并行执行 1000 个任务,所以这些任务中的大多数在执行前都已排队。这会产生很大的管理和任务切换开销,这就解释了糟糕的性能。

关于您添加的最后一个问题:如果外部任务的唯一目的是启动内部任务,那么外部任务没有任何用处,但如果外部任务在那里执行内部任务的某种协调tasks 那么它可能有意义(也许你想结合 Task.WhenAnyTask.WhenAll)。没有更多的上下文,很难回答。但是,您的问题似乎与性能有关,开始 100,000 个任务可能会增加相当大的开销。

Parallel.ForEach 如果您想像示例中那样执行 100,000 次独立计算,是一个不错的选择。任务非常适合执行并发事件,这些事件涉及对其他系统的“缓慢”调用,您希望在这些系统中等待并合并结果并处理错误。对于大规模并行性,它们可能不是最佳选择。

关于c# - WhenAll 与 WaitAll 并行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27527253/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com