短路等待任何顺序的所有任务

时间:2012-09-26 16:32:14

标签: c# async-await

我需要对Web服务进行一系列调用以获取一组节点计数。我正在异步并行地进行所有调用。在我发起呼叫之后,我将结果总结如下:

//pendingTasks is List<Task<int>>
int sum = 0;
foreach (var task in pendingTasks)
{
    sum += await task;
    if (sum > 100) break;
}

休息是因为我不关心超过100后的具体数量。

首先,像这样摆脱循环是危险的吗?离开待处理的任务是不是很糟糕?它会造成任何类型的内存泄漏吗?

其次,个别电话相当不一致。如果第一个电话是最长的电话,我会讨厌它,我最后等待它,即使所有后续的电话总和超过100个。当个别结果回来时,添加到总和中会很好。命令他们收到。我之前使用过WhenAll,我很确定WhenAny是我想要的,但我不太确定如何在这种场景中使用它,我想在它们来时处理多个in,然后在完成所有操作后终止。

2 个答案:

答案 0 :(得分:4)

  

首先,像这样摆脱循环是危险的吗?离开待处理的任务是不是很糟糕?它会造成任何类型的内存泄漏吗?

它不会造成内存泄漏,但如果任务具有与之关联的取消令牌,则取消它们以避免不必要地执行额外工作将非常有用。

在.NET 4中,出现故障但没有“观察到”这些错误的任务会默认(故意)关闭一个进程但是.NET 4.5已经放宽了。

  

我之前使用过WhenAll,我很确定WhenAny是我想要的,但是我不太确定如何在这种情况下使用它,我希望在它们进入时处理多个,然后终止当他们都完成了。

WhenAny肯定可以在这里为你工作,但另一种方法是“神奇地”重新排序任务,以便你可以按照它们完成的顺序迭代它们。或者更确切地说,迭代新任务,这些任务按照原始任务的顺序得到相同的结果

我刚才写了一篇blog post on that very topic - 虽然这不是我的主意。基本上,您创建了一堆TaskCompletionSource个对象 - 每个原始任务一个 - 并为每个原始任务添加一个延续,以填充“下一个可用”任务完成源。

有关如何使用WhenAny的示例,您可以查看我的majority voting博客文章 - 但其缺点是需要进行大量的集合操作,n致电WhenAny。 “魔术重新排序”创建了一个新的任务集合,但随后只是为每个原始任务附加一个延续,所以没有什么真正等待所有这些任务......你可以一次迭代一个,等待每个一个又一个。

答案 1 :(得分:1)

Jon Skeet的回答以及与Stephen Toub的方法的联系都很棒。

另一种方法是使用TPL DataFlow库中的BufferBlock<int>。如果您有权修改任务的参数,则只需传递BufferBlockPost您的结果:

var buffer = new BufferBlock<int>();

//Run your tasks somehow like so:
YourAsyncFunctionThatPostsAnInt(buffer, cancellationTokenSource.Token)
...

int sum = 0;
while(sum < 100)
{
    sum += await buffer.ReceiveAsync()
}

buffer.Complete();
cancellationTokenSource.Cancel();

即使使用现有的任务,您也可以为它们添加延续,使其结果Post到缓冲区。取消令牌是短路执行的最佳方式。