【问题标题】:How to await Parallel Linq actions to complete如何等待并行 Linq 操作完成
【发布时间】:2014-12-22 01:12:09
【问题描述】:

我不确定我应该如何混合 plinq 和 async-await。假设我有如下界面

public interface IDoSomething (
    Task Do();
}

我有一个列表,我想并行执行并能够await 完成所有操作。

public async Task DoAll(IDoSomething[] doers) {
    //Execute all doers in parallel ideally using plinq and 
    //continue when all are complete
}

如何实现?我不确定如何从并行 linq 转到 Tasks,反之亦然。

我并不十分担心异常处理。理想情况下,第一个会触发并破坏整个过程,因为我计划在出错时丢弃整个过程。

编辑:很多人都在说Task.WaitAll。我知道这一点,但我的理解(除非有人可以证明)是它不会主动为您将事情并行化到多个可用的处理器内核。我具体要问的是双重的 -

  1. 如果我在 Plinq 操作中 awaitTask 是否会失去很多优势,因为它会安排一个新线程?

  2. 如果我 doers.AsParallel().ForAll(async d => await d.Do()) 平均需要大约 5 秒,我如何不同时旋转调用线程?

【问题讨论】:

  • await Task.WhenAll(....)
  • 当被问到“并行异步代码”的问题时,正确的答案通常是TPL Dataflow。但是一个简单的Task.WhenAll 结合Task.Runs 可能就足够了,就像@l3arnon 的回答一样。
  • @GeorgeMauer:PLINQ 用于并行化 CPU 密集型工作。 Async/await 用于并发异步工作(通常是 I/O-bound)。异步等待并行工作完成 (await Task.Run(() => { /* parallel stuff */ })) 是有意义的,但尝试并行化异步(通常是 I/O 绑定)工作就没有意义了。 WhenAll 本身可以给你异步并发,或者WhenAll 包装Task.Runs 可以给你一种异步并行foreach。但是,您实际上需要极其罕见的
  • @GeorgeMauer:顺便说一句,我尽量不在这里做自我广告,但老实说,我认为你会从my book 中受益。我将讨论并发、异步、并行、TPL 数据流、Rx 以及它们如何协同工作。

标签: c# linq task-parallel-library async-await parallel-extensions


【解决方案1】:

你要找的是这个:

public Task DoAllAsync(IEnumerable<IDoSomething> doers)
{
    return Task.WhenAll(doers.Select(doer => Task.Run(() => doer.Do())));
}

使用Task.Run 将使用ThreadPool 线程并行执行async 方法Do 的每个同步部分,而Task.WhenAll 异步等待同时执行的异步部分。


这是一个好主意如果您在这些 async 方法(即 await 之前的部分)中有大量同步部分,例如:

async Task Do()
{
    for (int i = 0; i < 10000; i++)
    {
        Math.Pow(i,i);
    }

    await Task.Delay(10000);
}

否则,不需要并行性,您可以同时触发异步操作并使用 Task.WhenAll 等待所有返回的任务:

public Task DoAllAsync(IEnumerable<IDoSomething> doers)
{
    return Task.WhenAll(doers.Select(doer => doer.Do()));
}

【讨论】:

  • 那么这样的东西和使用AsParallel()有什么区别呢?您必须了解为什么我对似乎忽略了一半问题的答案持怀疑态度。
  • @GeorgeMauer plinq 比 async-await 更老,因此无法处理异步方法。你可以使用AsParallel,但那又如何呢? plinq 的答案是使用ForAll,但它不能接受异步委托并且不返回可等待的任务。 async 做你想做的事的方式是Task.RunTask.WhenAll,问题的任何部分都没有被忽略。
  • @GeorgeMauer 我看到您在ForAll 选项中编辑了您的问题。这将创建一个非常危险的async void lambda 表达式:Async/Await Best Practices in Asynchronous Programming
  • @GeorgeMauer TPL 是 async-await 和 PLinq(以及 TPL Dataflow 等)的下划线框架。 Plinq 是一种并行处理的解决方案。 async-await 是比旧选项(例如 BeginXEndX)更好的异步编程解决方案。在大多数情况下,它们没有任何共同之处。在您的中,它们可能有,但它们不能很好地结合在一起。您可以查看使用 async 构建的 TPL 数据流和响应式扩展,它们可以填补 PLinq 留下的空白。
  • 好的,谢谢,那开始为我完成图片了。
【解决方案2】:
public async Task DoAll(IDoSomething[] doers) {
    //using ToArray to materialize the query right here
    //so we don't accidentally run it twice later.
    var tasks = doers.Select(d => Task.Run(()=>d.Do())).ToArray();
    await Task.WhenAll(tasks);
}

【讨论】:

  • 90% 确定这将异步连接所有内容,但默认情况下doers 不会并行运行。这是整个异步不是并行的事情。除非有什么特别告诉 TPL 为此使用并行性。对吗?
  • 我的意思是,除非 TPL 专门有一个调度程序来执行并行线程,否则它将一次只运行一个(尽管是异步的)类似于 javascript。 I'm pretty sure thats what point 3 in the async/await FAQ implies
  • 好的,所以使用 Task.Run 强制它进入 ThreadPool(或者默认调度程序发送它运行的任何地方......)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-09
  • 1970-01-01
  • 2021-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多