アプリケーションによっては、呼び出し元スレッドでタスクが完了するのを待ち合わせなければならないケースがあります。また、実行していたタスクから結果を受け取り、それに応じて処理を分けたい場合もあります。言うまでもなく、TPLにはこのようなニーズに答えるための機能が準備されています。今回はそれらを見ていきます。
完了の待機
実行したタスクを待機する方法はとっても簡単で、次のサンプルのようにTaskインスタンスのWaitメソッドを呼び出すだけです。これによりタスクが終了するまで呼び出し元スレッドがブロックされるので、結果としてタスクの完了を待機することができます。以下の例では完了までずっと待機するようになっていますが、それ以外にもタイムアウト時間が設定できるなどするオーバーロードが定義されています。
using System; using System.Threading.Tasks; namespace Sample13_TaskWait { class Program { static void Main() { Console.WriteLine("Begin"); var task = new Task(() => Console.WriteLine("Task is running")); task.Start(); task.Wait(); Console.WriteLine("End"); } } } //----- 結果 /* Begin Task is running End */
また、Taskクラスには、単一のタスクの待機だけでなく複数のタスクを待機できるようにする2つの静的なメソッドが定義されています。指定されたすべてのタスクが完了するまで呼び出し元スレッドをブロックするときに利用するのがWaitAllメソッドです。動作は以下のサンプルの通りです。タイムアウト時間を指定できるオーバーロードもあり、すべてのタスクがタイムアウト時間内に完了した場合はtrueを、完了しなかった場合はfalseを返すようになっています。
using System; using System.Threading.Tasks; namespace Sample14_TaskWaitAll { class Program { static void Main() { Console.WriteLine("Begin"); var tasks = new [] { Task.Factory.StartNew(() => Console.WriteLine("Task1 is running")), Task.Factory.StartNew(() => Console.WriteLine("Task2 is running")), Task.Factory.StartNew(() => Console.WriteLine("Task3 is running")), }; Task.WaitAll(tasks); Console.WriteLine("End"); } } } //----- 結果 (例) /* Begin Task1 is running Task3 is running Task2 is running End */
指定された複数のタスクのいずれかが完了するまで呼び出し元スレッドをブロックするときに利用するのがWaitAnyメソッドです。タイムアウト時間を指定したりできるオーバーロードもあります。WaitAnyメソッドは、最初に処理が完了した配列のインデックスを戻します。タイムアウトが発生した場合は-1を返します。
using System; using System.Threading.Tasks; namespace Sample15_TaskWaitAny { class Program { static void Main() { Console.WriteLine("Begin"); var tasks = new [] { Task.Factory.StartNew(() => Console.WriteLine("Task1 is running")), Task.Factory.StartNew(() => Console.WriteLine("Task2 is running")), Task.Factory.StartNew(() => Console.WriteLine("Task3 is running")), }; int index = Task.WaitAny(tasks); Console.WriteLine("Index = {0}", index); Console.WriteLine("End"); } } } //----- 結果 (例) /* Begin Task1 is running Index = 0 End Task3 is running */
このように、タスクの完了を待機する便利な方法が用意されていますので、ぜひ覚えておいてください。
結果の取得
タスクで行った処理の結果を取得する場合は、Task<TResult>クラスを利用します。Genericで指定する型は、結果として取得するデータ型になります。結果を返す方法は簡単で、タスクの戻り値をResultプロパティで取得するだけです。
using System; using System.Linq; using System.Threading.Tasks; namespace Sample16_TaskResult { class Program { static void Main() { Console.WriteLine("Begin"); var task = new Task<int>(() => Enumerable.Range(1, 10).Sum()); task.Start(); task.Wait(); //--- なくても良い Console.WriteLine("Sum = {0}", task.Result); Console.WriteLine("End"); } } } //----- 結果 /* Begin Sum = 55 End */
(タスクが完了していないと結果を取得できませんので)Resultプロパティは内部的にWaitメソッドを呼び出します。そのため、上記のサンプルではWaitメソッドがなくても結果は同じになります。
次回予告
今回はタスクの完了を待機する方法と、タスクから結果を取得する方法について見てきました。次回は完了したタスクから連続して別のタスクを実行する方法について見ていきたいと思います。