【问题标题】:How to call an async method from within a loop without awaiting?如何在不等待的情况下从循环中调用异步方法?
【发布时间】:2014-09-20 11:18:45
【问题描述】:

考虑这段代码,其中有一些工作在一个 for 循环中完成,然后是一个递归调用来处理子项。我想将 DoSomething(item) 和 GetItems(id) 转换为异步方法,但如果我在这里等待它们,for 循环将等待每次迭代完成后再继续,基本上失去了并行处理的好处。我怎样才能提高这种方法的性能?是否可以使用 async/await 来做到这一点?

public void DoWork(string id)
        {            
                var items = GetItems(id);  //takes time

                if (items == null)
                    return;

                Parallel.ForEach(items, item =>
                {
                    DoSomething(item); //takes time
                    DoWork(item.subItemId);                    
                });           
        }

【问题讨论】:

  • @ZombieSheep 我也问了另一个问题。两者是有区别的。另一个与递归有关,不讨论性能。这是一个 for 循环,我想知道如何在不等待的情况下让循环继续,同时在循环内执行异步操作。
  • 我建议您通过弄清楚过程的哪一部分是问题所在,或者将这两个问题结合起来,以获得更高质量的答案。这样,给出的任何建议都会考虑到其余信息。
  • 尽管它们是两个完全不同的问题——我不想把它们结合起来混淆讨论。另一个是关于 async/await 在递归方法中是否安全的问题。这是关于如何在循环中有效地使用 async/await。另一个根本与性能无关。
  • 您在DoSomething/DoWork 中所做的工作确实是异步的吗?如果不是,那么通过使它们异步,您将一无所获。如果是的话,只需在一个线程中启动每个线程并等待所有线程,如果你必须并行 - 我仍然会坚持使用Parallel.ForEach

标签: c# .net asynchronous recursion async-await


【解决方案1】:

您可以创建一系列任务,然后使用Task.WhenAll 等待它们全部完成,而不是使用Parallel.ForEach 循环遍历项目。由于您的代码还涉及递归,因此它会稍微复杂一些,您需要将 DoSomethingDoWork 组合成一个方法,我将其命名为 DoIt

async Task DoWork(String id) {
  var items = GetItems(id);
  if (items == null)
    return;
  var tasks = items.Select(DoIt);
  await Task.WhenAll(tasks);
}

async Task DoIt(Item item) {
  await DoSomething(item);
  await DoWork(item.subItemId);
}

混合使用 Parallel.ForEach 和 async/await 是个坏主意。 Parallel.ForEach 将允许您的代码并行执行,并且对于计算密集型但可并行化的算法,您可以获得最佳性能。但是 async/await 允许您的代码并发执行,例如重用在 IO 操作上阻塞的线程。

简化的Parallel.ForEach 将设置与您计算机上的 CPU 内核一样多的线程,然后将您正在迭代的项目分区以在这些线程中执行。因此,Parallel.ForEach 应该在调用堆栈的底部使用一次,然后它将工作分散到多个线程并等待它们完成。在每个线程中以递归方式调用Parallel.ForEach 简直是疯了,根本不会提高性能。

【讨论】:

  • 使用这种方法,如果递归调用是并行运行的任务的一部分,会不会有问题?在我的问题中,您可以看到 DoWork(),这是一个递归调用,在 DoSomething() 之后立即被调用。谢谢。
  • @Prabhu:我没有注意到递归调用。那么你绝对应该避免使用Parallel.ForEach,但我认为使用我所描述的任务没有任何问题。然后,您可以从递归中获得任务层次结构,只要您可以适应流程中所有任务的状态,就可以了。
  • 谢谢@Martin。正如您使用多行 DoSomethign 和 DoWork 所建议的那样,我在构建我的 lambda 表达式时遇到了麻烦。你能给我指点吗?另外,为什么要避免使用 Parallel.ForEach?
猜你喜欢
  • 1970-01-01
  • 2013-11-16
  • 2013-03-09
  • 1970-01-01
  • 2021-12-19
  • 2019-04-10
  • 2016-07-23
  • 1970-01-01
相关资源
最近更新 更多