First approach: issue all requests up-front one after the other, then await until all requests come back, and then filter the result. (svick's code also did this, but here I'm doing it without the intermediate 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);
Second approach: do the requests one after the other. This will take longer but it will make sure not to hammer the webservice with a huge onslaught of requests (assuming that MeetsCriteriaAsync goes out to a webservice...)
// Second approach: one by one
var filteredAddresses = new List<Uri>();
foreach (var a in filteredAddresses)
{
if (await MeetsCriteriaAsync(a)) filteredAddresses.Add(a);
}
Third approach: as for the second, but using a hypothetical C#8 feature "asynchronous streams". C#8 isn't out yet, and asynchronous streams aren't designed yet, but we can dream! The IAsyncEnumerable type already exists in RX, and hopefully they'll add more combinators for it. The nice thing about IAsyncEnumerable is that we can start consuming the first few filteredAddresses as soon as they come, rather than waiting for everything to be filtered first.
// Third approach: ???
IEnumerable<Uri> addresses = {...};
IAsyncEnumerable<Uri> filteredAddresses = addresses.WhereAsync(MeetsCriteriaAsync);
Fourth approach: maybe we don't want to hammer the webservice with all requests all at once, but we're happy to issue more than one request at a time. Maybe we did experiments and found that "three at a time" was a happy medium. NOTE: this code assumes a single-threaded execution context such as in UI programming or ASP.NET. If it's being run in a multi-threaded execution context then it needs a ConcurrentQueue and ConcurrentList instead.
// 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);
}
}
There are ways do to the fourth approach using the TPL dataflow library too.