最初のアプローチ: すべてのリクエストを前もって次々に発行し、すべてのリクエストが戻ってくるまで待ってから、結果をフィルタリングします。(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);
2 番目のアプローチ: 要求を次々に実行します。これには時間がかかりますが、膨大なリクエストの猛攻撃で 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);
}
3 番目のアプローチ: 2 番目と同様ですが、架空の C#8 機能「非同期ストリーム」を使用します。C#8 はまだリリースされておらず、非同期ストリームはまだ設計されていませんが、夢を見ることはできます! IAsyncEnumerable 型は RX に既に存在しており、うまくいけば、さらにコンビネータが追加されるでしょう。IAsyncEnumerable の良いところは、すべてが最初にフィルター処理されるのを待つのではなく、最初のいくつかのfilteredAddresses が来るとすぐに消費を開始できることです。
// Third approach: ???
IEnumerable<Uri> addresses = {...};
IAsyncEnumerable<Uri> filteredAddresses = addresses.WhereAsync(MeetsCriteriaAsync);
4 番目のアプローチ: Web サービスにすべてのリクエストを一度にぶつけたくないかもしれませんが、一度に複数のリクエストを発行しても問題ありません。たぶん、私たちは実験をして、「一度に3つ」が幸せな媒体であることを発見しました. 注: このコードは、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 データフロー ライブラリを使用して 4 番目のアプローチを行う方法もあります。