第一种方法:一个接一个地发出所有请求,然后等待所有请求返回,然后过滤结果。(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 尚未推出,异步流尚未设计,但我们可以梦想!RX 中已经存在 IAsyncEnumerable 类型,希望他们会为它添加更多组合子。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 数据流库的第四种方法的方法。