web-dev-qa-db-ja.com

LINQを使用してタスクのリストを非同期に待機する方法

次のように作成したタスクのリストがあります。

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();

...しかし、これはコンパイルされません。私は何が欠けていますか?それとも、このように物事を表現することは不可能ですか?

79

LINQはasyncコードでは完全に機能しませんが、これを行うことができます。

var tasks = foos.Select(DoSomethingAsync).ToList();
await Task.WhenAll(tasks);

すべてのタスクが同じタイプの値を返す場合、これを行うこともできます。

var results = await Task.WhenAll(tasks);

とてもいいです。 WhenAllは配列を返すため、メソッドは結果を直接返すことができると思います。

return await Task.WhenAll(tasks);
122
Stephen Cleary

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);
        }
    }
}
7
Clement

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が遅延ヒットします。

3
Boris Lipschitz

Task.WaitAllまたはTask.WhenAllの適切な方を使用してください。

1
L.B

Task.WhenAllはここでトリックを行う必要があります。

0
Ameen