第一种方法:问题的所有先后请求预先请求,然后等待所有请求返回,然后过滤结果。 (svick的代码也是这样做的,但在这里我没有使用ConcurrentQueue)。
// First approach: massive fan-out
var tasks = addresses.Select(async a => new { A = a, C = await MeetsCriteriaAsync(a) });
var addressesAndCriteria = await Task.WhenAll(tasks);
var filteredAddresses = addressAndCriteria.Where(ac => ac.C).Select(ac => ac.A);
第二种方法:一个接一个地执行请求。这将需要更长的时间,但它会确保不与请求的巨大冲击锤的web服务(假设MeetsCriteriaAsync出去一个web服务...)
// Second approach: one by one
var filteredAddresses = new List<Uri>();
foreach (var a in filteredAddresses)
{
if (await MeetsCriteriaAsync(a)) filteredAddresses.Add(a);
}
第三种方法:对于第二,但使用一个假想的C#8特性“异步流”。 C#8还没有出来,异步流还没有设计,但我们可以做梦! IAsyncEnumerable类型已经存在于RX中,并且希望它们会为它添加更多的组合器。关于IAsyncEnumerable的好处在于,我们可以在开始使用前几个filteredAddresses时立即开始消费,而不是等待所有要先过滤的东西。
// Third approach: ???
IEnumerable<Uri> addresses = {...};
IAsyncEnumerable<Uri> filteredAddresses = addresses.WhereAsync(MeetsCriteriaAsync);
第四种方法:也许我们不想一下子锤所有请求的web服务,但我们很乐意向在同一时间超过一个请求。也许我们做了实验,发现“一次三个”是一个快乐的媒介。注意:此代码假设单线程执行上下文,如在UI编程或ASP.NET中。如果它在多线程执行上下文中运行,那么它需要一个ConcurrentQueue和ConcurrentList。
// Fourth approach: throttle to three-at-a-time requests
var addresses = new Queue<Uri>(...);
var filteredAddresses = new List<Uri>();
var worker1 = FilterAsync(addresses, filteredAddresses);
var worker2 = FilterAsync(addresses, filteredAddresses);
var worker3 = FilterAsync(addresses, filteredAddresses);
await Task.WhenAll(worker1, worker2, worker3);
async Task FilterAsync(Queue<Uri> q, List<Uri> r)
{
while (q.Count > 0)
{
var item = q.Dequeue();
if (await MeetsCriteriaAsync(item)) r.Add(item);
}
}
对于使用TPL数据流库的第四种方法也有一些办法。
如果支持这项功能,您会发生什么?特别是当迭代'filteredAddresses'时,实际调用'MeetsCriteria'。 – 2013-02-15 07:50:04
@DanielHilgarth:谢谢;那是个很好的观点。这似乎并不适合LINQ。 – Sam 2013-02-17 22:27:53