3

我需要对 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是我想要的,但我不太确定在这种情况下如何使用它,我想在它们进入时处理多个,然后在它们全部完成时终止.

4

2 回答 2

4

首先,像这样跳出循环有危险吗?留下待处理的任务是不是很糟糕?它会造成任何形式的内存泄漏吗?

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

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

我以前用过WhenAll,我很确定WhenAny是我想要的,但我不太确定如何在这种情况下使用它,我想在它们进入时处理多个,然后在它们出现时终止全部做完。

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

不久前我写了一篇关于这个主题的博客文章——尽管这不是我的主意。基本上,您创建一堆TaskCompletionSource对象 - 每个原始任务一个对象 - 并为每个原始任务添加一个延续,以填充“下一个可用”任务完成源。

有关如何使用. WhenAny_ _ “魔术重新排序”创建了一个新的任务集合,但随后只是为每个原始任务附加了一个延续,所以没有什么真正等待所有这些......你可以一次迭代它们,等待每个一个轮流。nWhenAny

于 2012-09-26T16:37:11.767 回答
1

Jon Skeet 的回答和 Stephen Toub 方法的链接都很棒。

另一种方法是使用BufferBlock<int>TPL DataFlow 库中的一个。如果您有权编辑任务的参数,您可以简单地传入 aBufferBlockPost您的结果:

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到缓冲区的结果中。取消令牌是缩短其执行的最佳方式。

于 2012-09-26T21:03:20.500 回答