LINQを使用して非同期でタスクのリストを待つ方法は?


87

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

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

...しかし、これはコンパイルされません。何が足りないのですか?それとも、このように表現することは不可能ですか?


DoSomethingAsync(foo)fooごとにシリアルに処理する必要がありますか、それともParallel.ForEach <Foo>の候補ですか?
mdisibio 2014

1
@ mdisibio-Parallel.ForEachブロックしています。ここでのパターンは、PluralsightのJon Skeetの非同期C#ビデオからのものです。ブロッキングなしで並行して実行されます。
マットジョンソン-パイント2014

@ mdisibio-いいえ。それらは並行して実行されます。 それを試してみてください。(さらに、.ToList()使用するだけの場合は必要ないようですWhenAll。)
Matt Johnson-Pint 2014

取られたポイント。DoSomethingAsync記述方法に応じて、リストは並行して実行される場合とされない場合があります。あるテストメソッドとそうでないバージョンを書くことができましたが、どちらの場合も、動作は、タスクを作成するデリゲートではなく、メソッド自体によって決定されます。取り違えてすみません。ただし、DoSomethingAsyc戻りTask<Foo>、その後、awaitデリゲートでは絶対に必要というわけではない...私はそれは私が作るしようとしていた主なポイントだったと思います。
mdisibio 2014

回答:


136

LINQはasyncコードでは完全には機能しませんが、次のことができます。

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

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

var results = await Task.WhenAll(tasks);

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

return await Task.WhenAll(tasks);

11
これも機能することを指摘したかったvar tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
mdisibio 2014

1
またはvar tasks = foos.Select(DoSomethingAsync).ToList();
Todd Menier

3
Linqが非同期コードで完全に機能しない理由は何ですか?
Ehsan Sajjad 2016

2
@EhsanSajjad:LINQ toObjectsはメモリ内オブジェクトで同期的に機能するためです。のように、いくつかの制限されたものが機能しSelectます。しかし、ほとんどはそうではありませんWhere
スティーブンクリアリー2016

4
@EhsanSajjad:操作がI / Oベースの場合、asyncスレッドを減らすために使用できます。CPUにバインドされていて、すでにバックグラウンドスレッド上にある場合asyncは、何のメリットもありません。
スティーブンクリアリー2016

9

Stephenの答えを拡張するために、LINQの流暢なスタイルを維持するために次の拡張メソッドを作成しました。その後、あなたはすることができます

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

10
個人的に、私はあなたの拡張メソッドに名前を付けますToArrayAsync
torvin 2015年

4

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



弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.