【问题标题】:Multiple async/await calls in a foreach loop iterationforeach 循环迭代中的多个 async/await 调用
【发布时间】:2016-02-15 00:19:54
【问题描述】:

我正在努力思考如何在 foreach 循环中处理多个 async/await 调用。我有大约 20,000 行由 foreach 循环处理的数据。我的代码大致是:

foreach (var item in data)
{
    if (ConditionA(item))
    {
        if (ConditionAB(item));
        {
            await CreateThingViaAPICall(item)
        }
        else
        {
            var result = await GetExistingRecord(item);
            var result2 = await GetOtherExistingRecord(result);
            var result3 = await GetOtherExistingRecord(result2);
            //Do processing
            ...           
            await CreateThingViaAPICall();
        }
    }
    ... and so on        
}

我看到很多帖子说在循环中使用异步的最佳方法是构建任务列表,然后使用 Task.WhenAll。在我的情况下,作为每次迭代的一部分,我有相互依赖的任务。在这种情况下,如何建立要执行的任务列表?

【问题讨论】:

  • 你有什么问题?
  • 如果我没记错的话,在 foreach 中使用 async/wait 的推荐方法是先构建一个任务列表,然后调用 Task.WhenAll。我的问题是循环的每次迭代都有多个任务相互依赖。在这种情况下,如何构建要等待的任务列表?
  • 我误解了你的问题吗?迭代中的每一项是否都依赖于上一项的成功完成?
  • 迭代中的每个项目确实相互依赖。你的回答真的很有帮助,我可以选择两者作为答案。
  • 啊,好吧,那我确实做了一个错误的假设。我不是打算刷已接受的答案,但我很高兴它有帮助:)

标签: c# asynchronous foreach async-await


【解决方案1】:

如果您将单个项目的处理分解为单独的(异步)方法,这是最简单的:

private async Task ProcessItemAsync(Item item)
{
    if (ConditionA(item))
    {
        if (ConditionAB(item));
        {
            await CreateThingViaAPICall(item)
        }
        else
        {
            var result = await GetExistingRecord(item);
            var result2 = await GetOtherExistingRecord(result);
            var result3 = await GetOtherExistingRecord(result2);
            //Do processing
            ...           
            await CreateThingViaAPICall();
        }
    }
    ... and so on
}

然后像这样处理您的收藏:

var tasks = data.Select(ProcessItemAsync);
await Task.WhenAll(tasks);

这有效地将处理单个项目所需的多个相关任务包装到一个任务中,允许这些步骤按顺序发生,同时同时处理集合本身的项目。

对于成千上万个项目,您可能会出于各种原因发现需要限制并发运行的任务数。查看此类场景的 TPL 数据流。有关示例,请参阅here

【讨论】:

  • 好吧,这很有意义,结合@jon-hanna 的回答更有意义。
  • 我理解您的问题的方式是项目 A 和项目 B 可以同时处理,但处理单个项目所涉及的步骤必须按顺序进行。这就是这种方法为您提供的。
【解决方案2】:

如果我没记错的话,在 foreach 中使用 async/wait 的推荐方法是先构建一个任务列表,然后调用 Task.WhenAll。

你错了。

如果您有多个不依赖于彼此的任务,那么将这些多个任务发生在WhenAll 中确实是一个非常好的主意,这样它们就可以安排在一起,从而提供更好的吞吐量。

但是,如果每个任务都依赖于前一个任务的结果,那么这种方法是不可行的。相反,您应该只在 foreach 中使用 await 它们。

确实,这在任何情况下都可以正常工作,如果不需要,让任务相互等待只是次优的。

awaitforeach 中执行任务的能力实际上是async/await 给我们带来的最大收获之一。大多数使用await 的代码都可以很容易地重写为使用ContinueWith,虽然不那么优雅,但是循环更棘手,如果循环的实际结束只能通过检查任务本身的结果来找到,那就更棘手了.

【讨论】:

  • 所以使用 Task.WhenAll 的建议是与性能相关的建议,而不是“如果你这样做会发生坏事”的建议?
  • 是的。现在,当它适用时,它可以产生 的不同,所以一定要尽可能地采用这种方法。您甚至可以进行混合;任务块,其中每个块中的每个块相互依赖,但每个块可以是独立的。
猜你喜欢
  • 2022-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-16
  • 1970-01-01
  • 2017-04-29
  • 2016-12-30
相关资源
最近更新 更多