在foreach循环迭代中进行多次异步/等待调用

时间:2016-02-15 00:19:55

标签: c# asynchronous foreach async-await

我试图围绕如何在foreach循环中处理多个异步/等待调用。我有大约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。在我的情况下,我有任务作为每次迭代的一部分相互依赖。在这种情况下,如何构建要执行的任务列表?

2 个答案:

答案 0 :(得分:3)

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

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 Dataflow。有关示例,请参阅here

答案 1 :(得分:1)

  

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

你有些错误。

如果您有多个不相互依赖的任务,那么在WhenAll中执行这些多项任务通常是一个非常好的主意,这样它们可以一起安排,从而提供更好的吞吐量

但是,如果每个任务都取决于之前的结果,那么这种方法是不可行的。相反,您应该在awaitforeach {。}}。{/ 1}

事实上,这对任何情况都可以正常工作,如果他们没有必要等待任务,那就太不理想了。

awaitforeach个任务的能力实际上是async / await给我们带来的最大收益之一。使用await的大多数代码都可以很容易地重写以使用ContinueWith,如果不太优雅,但循环更棘手,如果只通过检查任务的结果找到循环的实际结束他们自己,再次变得棘手。