web-dev-qa-db-ja.com

タスクを並行して実行する

わかりましたので、基本的に私はたくさんのタスクを持っています(10)、それらをすべて同時に開始し、完了するのを待ちたいです。完了したら、他のタスクを実行します。私はこれについて多くのリソースを読みましたが、特定の場合にそれを正しく得ることができません...

ここに私が現在持っているものがあります(コードは単純化されています):

public async Task RunTasks()
{
    var tasks = new List<Task>
    {
        new Task(async () => await DoWork()),
        //and so on with the other 9 similar tasks
    }


    Parallel.ForEach(tasks, task =>
    {
        task.Start();
    });

    Task.WhenAll(tasks).ContinueWith(done =>
    {
        //Run the other tasks
    });
}

//This function perform some I/O operations
public async Task DoWork()
{
    var results = await GetDataFromDatabaseAsync();
    foreach (var result in results)
    {
        await ReadFromNetwork(result.Url);
    }
}

だから私の問題は、WhenAll呼び出しでタスクが完了するのを待っているときに、どれも完了していなくてもすべてのタスクが終了したことを教えてくれることです。 foreachConsole.WriteLineを追加しようとしましたが、継続タスクに入ったときに、まだ完了していない以前のTasksからデータが入り続けています。

ここで何が間違っていますか?

25
Yann Thibodeau

Taskコンストラクターを直接使用することはほとんどありません。あなたの場合、そのタスクはあなたが待つことができない実際のタスクを起動するだけです。

単にDoWorkを呼び出してタスクを取得し、リストに保存して、すべてのタスクが完了するのを待つことができます。意味:

tasks.Add(DoWork());
// ...
await Task.WhenAll(tasks);

ただし、非同期メソッドは、未完了タスクの最初の待機に達するまで同期的に実行されます。その部分に時間がかかりすぎることが心配な場合は、Task.Runを別のThreadPoolスレッドにオフロードしてから、リストにthatタスクを保存します。

tasks.Add(Task.Run(() => DoWork()));
// ...
await Task.WhenAll(tasks);
29
i3arnon

基本的に、互換性のない2つの非同期パラダイムが混在しています。すなわちParallel.ForEach()および_async-await_。

あなたが望むもののために、どちらかを行います。例えば。 Parallel.For[Each]()を使用して、async-awaitを完全に削除できます。 Parallel.For[Each]()は、すべての並列タスクが完了したときにのみ戻り、他のタスクに移動できます。

コードには他にもいくつかの問題があります。

  • メソッドを非同期とマークしますが、待機しません(待機するのはメソッドではなくデリゲートです)。

  • 特にUIスレッドで結果をすぐに使用しようとしていない場合は、ほとんど確実に.ConfigureAwait(false)を待ちます。

8
sellotape

これらのタスクをTPLを使用して異なるスレッドで並列実行したい場合、次のようなものが必要になる場合があります。

public async Task RunTasks()
{
    var tasks = new List<Func<Task>>
    {
       DoWork,
       //...
    };

    await Task.WhenAll(tasks.AsParallel().Select(async task => await task()));

    //Run the other tasks
}

これらのアプローチは、スレッドプールへのメソッドのキューイングと未完了のTaskの返送という少量のコードのみを並列化します。また、このような少量のタスクでは、並列化は単に非同期で実行するよりも時間がかかる場合があります。これは、タスクが最初に待機する前に、より長い(同期)作業を行う場合にのみ意味があります。

ほとんどの場合、より良い方法は次のとおりです。

public async Task RunTasks()
{
    await Task.WhenAll(new [] 
    {
        DoWork(),
        //...
    });
    //Run the other tasks
}

あなたのコードの私の意見に:

  1. Parallel.ForEachに渡す前に、コードをTaskでラップしないでください。

  2. awaitを使用する代わりに、ただContinueWithTask.WhenAllとすることができます。

3
Andrey Tretyak

DoWorkメソッドは、非同期I/Oメソッドです。ほとんどの場合、メソッドは非同期的にI/Oの完了を待機するため、複数のスレッドを実行する必要はありません。 1つのスレッドで十分です。

public async Task RunTasks()
{
    var tasks = new List<Task>
    {
        DoWork(),
        //and so on with the other 9 similar tasks
    };

    await Task.WhenAll(tasks);

    //Run the other tasks            
}

新しいタスクを作成するには、ほとんど Taskコンストラクターを使用しない にする必要があります。非同期I/Oタスクを作成するには、単にasyncメソッドを呼び出します。スレッドプールスレッドで実行されるタスクを作成するには、Task.Runを使用します。 Task.Runおよびタスク作成の他のオプションの詳細な説明については、 この記事 を参照してください。

1
Jakub Lortz

また、Task.WhenAllの周りにtry-catchブロックを追加するだけです

注意:発生した1つ以上の例外のラッパーとして機能するSystem.AggregateExceptionのインスタンスがスローされます。これは、Task.WaitAll()やTask.WaitAny()などの複数のタスクを調整するメソッドにとって重要であるため、AggregateExceptionは、発生した実行中のタスク内のすべての例外をラップできます。

try
{ 
    Task.WaitAll(tasks.ToArray());  
}
catch(AggregateException ex)
{ 
    foreach (Exception inner in ex.InnerExceptions)
    {
    Console.WriteLine(String.Format("Exception type {0} from {1}", inner.GetType(), inner.Source));
    }
}
0
NiTRiX-Reloaded