複数の非同期タスクを実行し、それらがすべて完了するのを待つ


265

コンソールアプリケーションで複数の非同期タスクを実行し、それらがすべて完了するのを待ってから、さらに処理する必要があります。

世の中にはたくさんの記事がありますが、読むほど混乱するようです。タスクライブラリの基本原則を読み、理解しましたが、どこかにリンクがありません。

タスクをチェーンして別のタスクが完了した後に開始することは可能であることを理解しています(これは、私が読んだすべての記事のシナリオとほぼ同じです)、すべてのタスクを同時に実行したいので、一度知りたいそれらはすべて完了しました。

このようなシナリオの最も簡単な実装は何ですか?

回答:


440

どちらの回答も待ち受けていることについては触れていません Task.WhenAll

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

主な違いは Task.WaitAllとは、Task.WhenAll前者が(使用と同様のブロックであろうということですWait後者は単一のタスクには)、全てのタスクが完了するまで、呼び出し元に制御用バックを得待つことができないであろう。

さらに、例外処理は異なります。

Task.WaitAll

少なくとも1つのタスクインスタンスがキャンセルされました。または、少なくとも1つのタスクインスタンスの実行中に例外がスローされました。タスクがキャンセルされた場合、AggregateExceptionのInnerExceptionsコレクションにOperationCanceledExceptionが含まれます。

Task.WhenAll

提供されたタスクのいずれかがフォルト状態で完了すると、返されたタスクもフォルト状態で完了します。その例外には、提供された各タスクからのラップされていない例外のセットの集約が例外に含まれます。

提供されたタスクのどれにも障害がなく、少なくとも1つがキャンセルされた場合、返されたタスクはキャンセルされた状態で終了します。

失敗したタスクもキャンセルされたタスクもない場合、結果のタスクはRanToCompletion状態で終了します。提供された配列/列挙型にタスクが含まれていない場合、返されたタスクは、呼び出し元に返される前に、すぐにRanToCompletion状態に遷移します。


4
これを試すと、タスクが順番に実行されますか?前に各タスクを個別に開始する必要がありますawait Task.WhenAll(task1, task2);か?
Zapnologica

4
@Zapnologica Task.WhenAllはタスクを開始しません。あなたはそれらを「ホット」に提供する必要があります。つまり、すでに開始されています。
Yuval Itzchakov

2
OK。それは理にかなっている。それであなたの例は何をしますか?あなたがそれらを始めていないので?
Zapnologica

2
@YuvalItzchakovありがとうございます!とてもシンプルですが、今日はとても役に立ちました!少なくとも+1000の価値があります:)
DanielDušek

1
@Pierre私はフォローしていません。StartNew新しいタスクをスピンすることは、それらすべてを非同期的に待機することと何が関係していますか?
Yuval Itzchakov

106

次のような多くのタスクを作成できます。

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

48
WhenAll
Ravi

.Start()ではなくawaitキーワードを使用して、同時に複数の新しいスレッドを開始することは可能ですか?
Matt W

1
@MattWいいえ、awaitを使用すると、完了するまで待機します。この場合、マルチスレッド環境を作成することはできません。これが、ループの終わりにすべてのタスクが待機する理由です。
ウイルス、

5
これがブロッキングコールであることが明確にされていないため、将来の読者に反対票を投じてください。
JRoughan 2016年

これを行わない理由については、承認された回答を参照してください。
EL MOJO 2017

26

私が見た中で最良のオプションは、次の拡張メソッドです。

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

次のように呼び出します。

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

または非同期ラムダで:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

26

WhenAllwhichを使用して、待機可能なものを返すTaskWaitAll、戻り値の型がなくThread.Sleep、すべてのタスクが完了するか、キャンセルされるか、障害が発生するまで、同様のコード実行をブロックします。

ここに画像の説明を入力してください

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

タスクを特定の順序で実行する場合は、このアンサーからインスピレーションを得ることができます。


申し訳ありません後半のパーティーに来てますが、なぜあなたが持っているかawait、各操作のために、同じ時間の使用で、WaitAllまたはWhenAllTask[]初期化のタスクはなくてはなりませawaitんか?
dee zg

@dee zgそうですね。上記の待機は目的を無効にします。回答を変更して削除します。
NtFreX

はい、それだけです。説明してくれてありがとう!(いい答えに
賛成

8

をチェーン化しますTaskか、それとも並行して呼び出すことができますか?

チェイニングのため
だけのような何かを

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

障害が発生している可能性があるためTask、それぞれの前のインスタンスを確認することを忘れないでくださいContinueWith

並列方式
Parallel.Invoke 場合私が出会った最も単純な方法:それ以外の場合、Task.WaitAllまたはWaitHandlesを使用して、アクションをゼロにカウントダウンすることもできます(待機、新しいクラスがあります:) CountdownEvent、または...


3
答えを感謝しますが、あなたの提案はもう少し説明されているかもしれません。
Daniel Minnaar 14

@drminnaar例のあるmsdnへのリンクの横にある他のどの説明が必要ですか?リンクをクリックすることすらしませんでしたね。
Andreas Niedermair 2014

4
リンクをクリックして内容を読みました。私はInvokeを使用していましたが、IfとButがたくさんありましたが、非同期で実行されるかどうかについてです。あなたはあなたの答えを継続的に編集していました。あなたが投稿したWaitAllリンクは完璧でしたが、私は同じ機能をより速く、より読みやすい方法で示す答えを探しました。腹を立てないでください、あなたの答えはまだ他のアプローチに良い代替手段を提供します。
Daniel Minnaar 14

@drminnaar違反はありません、私は興味があります:)
Andreas Niedermair 14

5

これは私が配列Func <>でそれを行う方法です:

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

1
タスク配列として保持しないのはなぜですか?
Talha TalipAçıkgöz17年

1
@talha-talip-açıkgözを慎重に行わない場合は、タスクの実行が予想されていなかったときにタスクを実行します。Funcデリゲートとしてそれを行うと、意図が明確になります。
DalSoft

5

さらに別の答え...しかし、私は通常、データを同時にロードして変数に入れる必要がある場合に自分自身を見つけます:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

1
データベース呼び出しだけの場合LoadCatsAsync()LoadDogAsync()それらはIOバウンドです。Task.Run()CPUバウンド作業用です。データベースサーバーからの応答を待っているだけの場合は、不要なオーバーヘッドが追加されます。Yuvalの受け入れられた答えは、IOにバインドされた作業のための正しい方法です。
スティーブンケネディ

@StephenKennedyは、どのようなオーバーヘッドがあり、それがパフォーマンスにどの程度影響する可能性があるかを明確にしていただけませんか?ありがとう!
Yehor Hromadskyi

コメントボックスに要約するのは非常に難しいでしょう:)代わりに、Stephen Clearyの記事を読むことをお勧めします-彼はこのことの専門家です。ここからスタート:blog.stephencleary.com/2013/10/...
スティーブン・ケネディ

-1

これらのシナリオのいくつかでタスクを使用する方法を示すコードを用意しました。

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

1
タスクの結果をどのように取得しますか?たとえば、データテーブルの「行」(Nタスクからの並列)をマージし、それをgridview asp.netにバインドしますか?
PreguntonCojoneroCabrón
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.