xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

TPL入門 (9) - 完了の待機と結果の取得

アプリケーションによっては、呼び出し元スレッドでタスクが完了するのを待ち合わせなければならないケースがあります。また、実行していたタスクから結果を受け取り、それに応じて処理を分けたい場合もあります。言うまでもなく、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メソッドがなくても結果は同じになります。

次回予告

今回はタスクの完了を待機する方法と、タスクから結果を取得する方法について見てきました。次回は完了したタスクから連続して別のタスクを実行する方法について見ていきたいと思います。