第一種方法:問題的所有先後請求預先請求,然後等待所有請求返回,然後過濾結果。 (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