わかりましたので、基本的に私はたくさんのタスクを持っています(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
呼び出しでタスクが完了するのを待っているときに、どれも完了していなくてもすべてのタスクが終了したことを教えてくれることです。 foreach
にConsole.WriteLine
を追加しようとしましたが、継続タスクに入ったときに、まだ完了していない以前のTask
sからデータが入り続けています。
ここで何が間違っていますか?
Task
コンストラクターを直接使用することはほとんどありません。あなたの場合、そのタスクはあなたが待つことができない実際のタスクを起動するだけです。
単にDoWork
を呼び出してタスクを取得し、リストに保存して、すべてのタスクが完了するのを待つことができます。意味:
tasks.Add(DoWork());
// ...
await Task.WhenAll(tasks);
ただし、非同期メソッドは、未完了タスクの最初の待機に達するまで同期的に実行されます。その部分に時間がかかりすぎることが心配な場合は、Task.Run
を別のThreadPool
スレッドにオフロードしてから、リストにthatタスクを保存します。
tasks.Add(Task.Run(() => DoWork()));
// ...
await Task.WhenAll(tasks);
基本的に、互換性のない2つの非同期パラダイムが混在しています。すなわちParallel.ForEach()
および_async-await
_。
あなたが望むもののために、どちらかを行います。例えば。 Parallel.For[Each]()
を使用して、async-awaitを完全に削除できます。 Parallel.For[Each]()
は、すべての並列タスクが完了したときにのみ戻り、他のタスクに移動できます。
コードには他にもいくつかの問題があります。
メソッドを非同期とマークしますが、待機しません(待機するのはメソッドではなくデリゲートです)。
特にUIスレッドで結果をすぐに使用しようとしていない場合は、ほとんど確実に.ConfigureAwait(false)
を待ちます。
これらのタスクを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
}
あなたのコードの私の意見に:
Parallel.ForEach
に渡す前に、コードをTask
でラップしないでください。
await
を使用する代わりに、ただContinueWith
Task.WhenAll
とすることができます。
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
およびタスク作成の他のオプションの詳細な説明については、 この記事 を参照してください。
また、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));
}
}