次のように作成したタスクのリストがあります。
public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
var foos = await GetFoosAsync();
var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
...
}
.ToList()
を使用すると、タスクがすべて開始されます。今、私は彼らの完了を待って、結果を返したいです。
これは上記の...
ブロックで機能します:
var list = new List<Foo>();
foreach (var task in tasks)
list.Add(await task);
return list;
それは私が望むことをしますが、これはかなり不器用です。私はこのような簡単なものを書くほうがはるかに好きです:
return tasks.Select(async task => await task).ToList();
...しかし、これはコンパイルされません。私は何が欠けていますか?それとも、このように物事を表現することは不可能ですか?
LINQはasync
コードでは完全に機能しませんが、これを行うことができます。
var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);
すべてのタスクが同じタイプの値を返す場合、これを行うこともできます。
var results = await Task.WhenAll(tasks);
とてもいいです。 WhenAll
は配列を返すため、メソッドは結果を直接返すことができると思います。
return await Task.WhenAll(tasks);
Stephenの答えを拡張するために、LINQの 流fluentなスタイル を維持するために次の拡張メソッドを作成しました。その後、できます
await someTasks.WhenAll()
namespace System.Linq
{
public static class IEnumerableExtensions
{
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source)
{
return Task.WhenAll(source);
}
}
}
Task.WhenAllの1つの問題は、並列性が作成されることです。ほとんどの場合、それはさらに良いかもしれませんが、時々あなたはそれを避けたいです。たとえば、DBからバッチでデータを読み取り、データをリモートWebサービスに送信します。すべてのバッチをメモリにロードしたくはありませんが、前のバッチが処理された後にDBにヒットします。そのため、非同期性を破る必要があります。以下に例を示します。
var events = Enumerable.Range(0, totalCount/ batchSize)
.Select(x => x*batchSize)
.Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult())
.SelectMany(x => x);
foreach (var carEvent in events)
{
}
注.GetAwaiter()。GetResult()は、同期に変換します。イベントのbatchSizeが処理されると、DBが遅延ヒットします。
Task.WaitAll
またはTask.WhenAll
の適切な方を使用してください。
Task.WhenAllはここでトリックを行う必要があります。