Task.Run() はワーカースレッドに処理を投げることができ、簡単に非同期処理を実行できる便利な機能です。この記事では Task.Run メソッドで引数を持たせてスレッド処理を実行させる場合を、サンプルを交えながら解説していきます。
上記の記事では非同期処理における Task.Run の処理について解説しているので、分からないことがあればチェックしておくとよいでしょう。
引数のあるスレッド処理とは
単純な Task のスレッド処理では引数を渡さないサンプルを紹介してきました。Task.Run() では、ラムダ式で記述したデリゲートを引数として渡すことが可能です。デリゲートで渡すことで呼び出し元で使っているデータをワーカースレッドから参照することも可能となります。
それぞれのスレッドが独立している場合にデータの参照によっては、できること・できないことが発生してしまうのを防ぐことができ、より汎用的にワーカースレッドを活用することが可能となります。
引数のあるスレッド処理のサンプル
それでは実際に引数を持たせて非同期処理を行う方法のサンプルと解説を行います。共通化した処理などをパラメータで切り替えて処理させる場合など、より非同期処理を活用するために有効なので知っておくべき記述方法です。今回も .Net でのコンソールアプリケーションを作成しています。
//タスクを分けて実行する
Task.Run(() => DoHeavyWork("太郎"));
Task.Run(() => DoHeavyWork("花子"));
Console.ReadLine();
//非同期処理を切り出したメソッド
static void DoHeavyWork(string name)
{
Console.WriteLine($"{name}が重い処理を開始します。");
Thread.Sleep(3000);
Console.WriteLine($"{name}は仕事中です。");
Thread.Sleep(3000);
Console.WriteLine($"{name}が重い処理が終了しました。");
}
上記の処理を実行すると結果は以下のように表示されます。非同期処理で実装しているため、花子や太郎の順番は異なっている場合があるかもしれません。それは CPU の状態によるものです。
花子が重い処理を開始します。
太郎が重い処理を開始します。
太郎は仕事中です。
花子は仕事中です。
太郎が重い処理が終了しました。
花子が重い処理が終了しました。
今回は非同期処理をメソッドとして共通化して切り出しておきました。Task.Run() でデリゲートとして引数を渡す場合は、行いたい処理をメソッドで切り分けておくと使い勝手が向上しますね。
ワーカースレッドで処理をするということは、花子が処理中の路線と太郎が処理中の路線はそれぞれメインスレッドから独立しており、互いに干渉できなくなる状態となります。路線が分岐する際に、メソッドに切り分けておくことで処理に必要な情報が明確にできます。サンプルのメソッドでは引数に「名前」が必要であることが外部にも伝わります。
次は、上記のサンプルに手を加えて同期的に動くようにしてみます。
Console.WriteLine("仕事を依頼します。");
var task1 = Task.Run(() => DoHeavyWork("太郎"));
var task2 = Task.Run(() => DoHeavyWork("花子"));
Console.WriteLine("仕事を依頼しました。終わるまで待ちましょう。");
//すべてが完了するまで待機する
Task.WaitAll(new Task[] { task1, task2 });
Console.WriteLine("依頼した仕事が終わったようです。お疲れ様でした。");
Console.ReadLine();
//非同期処理を切り出したメソッド
static void DoHeavyWork(string name)
{
Console.WriteLine($"{name}が重い処理を開始します。");
Thread.Sleep(3000);
Console.WriteLine($"{name}は仕事中です。");
Thread.Sleep(3000);
Console.WriteLine($"{name}が重い処理が終了しました。");
}
上記の実行結果を予測できるでしょうか。初めて Task.WaitAll(); が出てきましたが、以前の task.Wait(); と同じようなものと考えてください。要するにすべての仕事が終わるのを待っている、とのをコード上で示していることになります。
仕事を依頼します。
仕事を依頼しました。終わるまで待ちましょう。
太郎が重い処理を開始します。
花子が重い処理を開始します。
太郎は仕事中です。
花子は仕事中です。
花子が重い処理が終了しました。
太郎が重い処理が終了しました。
依頼した仕事が終わったようです。お疲れ様でした。
上のサンプルを実行すると上記のような実行結果を出力してくれます。「仕事を依頼しました。終わるまで待ちましょう。」を出力した後は、「Task.WaitAll(new Task[] { task1, task2 });」で記述したように、WaitAll メソッドに渡した Task の処理がすべて終わるまで待機してくれます。
複数の Task を管理する場合は、WaitAll メソッドを活用することで、複数の非同期処理の完了を待つまで待機させることができるので使ってみてください。また、 WaitAny メソッドもあり、こちらは「どれかの処理が完了するまで待機」という意味になります。
以上、引数を持つ Task の書き方を紹介しました。引数を持つ場合は処理部分をメソッドに切り出してしまうのも分かりやすいです。もちろん、デリゲートで処理が見えにくいと感じる場合は、戻り値を持つ場合のスレッド処理でも、実行部分をメソッドに切り出しても良いと思います。