bry*_*ryl 2 .net c# task-parallel-library async-await
我想了解如何WaitAll与WhenAll工作,并有以下问题.有两种方法可以从方法中获取结果:
return Task.WhenAll(tasks).Result.SelectMany(r=> r);return tasks.Select(t => t.Result).SelectMany(r => r).ToArray();如果我理解正确,第二种情况就像WaitAll在tasks此之后调用并获取结果.
看起来第二种情况有更好的表现.我知道正确使用的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);
}
Run Code Online (Sandbox Code Playgroud)
看起来问题在于从其他任务(或Parallel循环)启动任务.在这种情况下,WhenAll导致程序的性能更差.这是为什么?
您正在Parallel.ForEach循环中启动任务,您应该避免.整个过程Paralle.ForEach是在可用CPU核心上并行化许多小而密集的计算,并且启动任务不是一项密集计算.相反,如果任务池已经饱和,它会创建一个任务对象并将其存储在队列中,并且很快就会启动1000个任务.因此,现在Parallel.ForEach与计算资源的任务池竞争.
在第一个非常慢的循环中,似乎调度是次优的,并且可能因为Task.WhenAll内部使用的CPU很少Parallel.ForEach.如果您将其更改Parallel.ForEach为正常for循环,您将看到加速.
但是如果你的代码真的像Compute函数一样简单而没有在迭代之间进行任何状态,你就可以摆脱任务并简单地Parallel.ForEach用来最大化性能:
Parallel.For(0, 100, (i, s) =>
{
Enumerable.Range(1, 1000).Select(n => Compute(n)).SelectMany(r => r).ToList();
});
Run Code Online (Sandbox Code Playgroud)
至于为什么Task.WhenAll表现更差,你应该意识到这个代码
tasks.Select(t => t.Result).SelectMany(r => r).ToList();
Run Code Online (Sandbox Code Playgroud)
不会并行运行任务.在ToList基本包装在一个迭代foreach循环和循环体创建一个任务,然后等待任务完成,因为您检索的Task.Result属性.因此,循环的每次迭代都将创建一个任务,然后等待它完成.1000个任务一个接一个地执行,处理任务的开销很小.这意味着您不需要我上面建议的任务.
另一方面,代码
Task.WhenAll(tasks).Result.SelectMany(result => result).ToList();
Run Code Online (Sandbox Code Playgroud)
将启动所有任务并尝试同时执行它们,因为任务池无法并行执行1000个任务,大多数任务在执行前排队.这会产生很大的管理和任务切换开销,从而解释了糟糕的性能.
关于你添加的最后一个问题:如果外部任务的唯一目的是启动内部任务,那么外部任务没有用处,但如果外部任务是在那里执行内部任务的某种协调那么它可能是有意义的(也许你想结合Task.WhenAny使用Task.WhenAll).没有更多的背景,很难回答.但是,您的问题似乎与性能有关,启动100,000个任务可能会增加相当大的开销.
Parallel.ForEach如果你想像你的例子那样执行100,000次独立计算,那么这是一个不错的选择.任务非常适合执行并发活动,这些活动涉及对您要等待并组合结果并处理错误的其他系统的"慢速"调用.对于大规模并行性,它们可能不是最佳选择.
| 归档时间: |
|
| 查看次数: |
6548 次 |
| 最近记录: |