在并行启动多个异步任务方面,最佳方法是什么?

4

我有可能需要运行数千个独立任务。每个任务都可能会进行数据库调用,因此它们已经在可能的情况下利用了异步处理。也就是说,如果我想让它们全部并行运行,最好的方法是什么?

目前我的写法是这样的:

Parallel.For(0, items.Count, async _ => await PerformTask());

我也考虑过使用以下方法:

List<Task> tasks = new List<Task>();
for(var i = 0; i < items.Count; ++i) tasks.Add(PerformTask());
await Task.WhenAll(tasks); // or possibly Task.WaitAll(tasks.ToArray())

是否有一种客观最佳方法来实现这个?

编辑:这与已标记的重复问题不同,因为我不是在问区别,而是在问哪种方式对我的用例是正确的。


1
可能是Parallel.ForEach vs Task.Run and Task.WhenAll的重复问题。 - Liam
对于最后一部分,Task.WhenAll 可能更好,因为它返回一个任务。Task.WaitAll 返回 void,并且会阻塞直到所有任务完成。 - Jonesopolis
@Liam,不是重复的,看编辑。链接答案上的回答实际上没有说明在特定情况下应该使用什么。而且似乎没有一个固定的“只能这样做”的答案。 - Charles
@Charles,如果你需要更细粒度地控制每个任务,那么你应该使用Task,否则就选择Paralell.For。 - Vinod
@Vinod,这回答了问题,但你能详细解释一下吗? - Charles
1个回答

3

Parallel 不是一个选项,因为你有异步操作。

你的选择是:

  • 同时开始所有任务,然后使用 await Task.WhenAll 等待它们全部完成。如果需要限制活动任务的数量,可以使用 SemaphoreSlim
  • 使用 ActionBlock<T>(来自 TPL Dataflow)逐个排队处理工作。如果要同时处理多个任务,则可以使用 ExecutionDataflowBlockOptions.MaxDegreeOfParallelism

如果你不知道所有任务在启动时的情况(即,如果在处理期间有更多的任务到达),或者如果你的代码中的其他部分与“管道”设计相适应,那么ActionBlock<T>方法会更好。

Task.WhenAll 很好,因为它不需要使用具有自己设计哲学和学习曲线的单独库。

无论是使用 Task.WhenAll 还是 ActionBlock<T>,都将很好地满足你的使用情况。


在我的情况下,我知道将要运行的每个任务,所以ActionBlock<T>失去了它的优势。总体而言,每个单独任务的处理时间都非常短,直到进行DB调用。我对查询池分配给我的操作感到满意,因此我将坚持使用Task.WhenAll。谢谢! - Charles

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接