xin9le.net

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

TPL入門 (11) - 入れ子タスクと子タスク

タスク内で別のタスクを作成/実行するという、入れ子のタスクを作ることができます。また、タスクに親子関係を持たせることも可能です。今回はこれらについて見ていきたいと思います。

入れ子タスク

入れ子タスクを作るのに何か特別な作業が必要かというと、そんなことはありません。タスクA上のスレッドとはまた別のスレッドでタスクBが実行されるというだけです。(すでにお分かりかとは思いますが)ここで注意が必要なのは、タスクBを待機するコードを明示しなければ、タスクAはタスクBの完了を待たずに終了するということです。以下にそのサンプルを示します。

using System;
using System.Threading;
using System.Threading.Tasks;
 
namespace Sample20_NestedTask
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Main : Begin");
            var task1 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task1 : Begin");
                var task2 = Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Task2 : Begin");
                    Thread.Sleep(3000);
                    Console.WriteLine("Task2 : End");
                });
                //task2.Wait();    //--- コメントの有無で結果が変わります
                Console.WriteLine("Task1 : End");
            });
            task1.Wait();
            Console.WriteLine("Main : End");
        }
    }
}
 
//----- 結果 (Task2の待機なし)
/*
Main : Begin
Task1 : Begin
Task1 : End
Main : End
Task2 : Begin
*/
 
//----- 結果 (Task2の待機あり)
/*
Main : Begin
Task1 : Begin
Task2 : Begin
Task2 : End
Task1 : End
Main : End
*/

Task2の処理に時間がかかっているため、Task2の待機を行わない場合は、その間にTask1もメインスレッドも終了しているのが分かると思います。

親子関係を持つタスク

タスクに親子関係を持たせるためには、タスクを生成する際にTaskCreationOptions.AttachedToParentを指定します。たったこれだけで、作成されるタスクを作成元のタスクと関連付け、タスクが実行を完了するまで作成元のタスクを完了したとみなさないようになります。以下にそのサンプルを示します。

using System;
using System.Threading;
using System.Threading.Tasks;
 
namespace Sample21_ParentChildTask
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Main : Begin");
            var task1 = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task1 : Begin");
                Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("Task2 : Begin");
                    Thread.Sleep(3000);
                    Console.WriteLine("Task2 : End");
                }, TaskCreationOptions.AttachedToParent);
                Console.WriteLine("Task1 : End");
            });
            task1.Wait();
            Console.WriteLine("Main : End");
        }
    }
}
 
//----- 結果
/*
Main : Begin
Task1 : Begin
Task1 : End
Task2 : Begin
Task2 : End
Main : End
*/

入れ子タスクのときにTask2を待機したのとはまた違う結果になっていることが分かります。Task1はTask2を実行して終了しようとしますが、Task2がTask1の子タスクになっているためTask2の完了を待っているため、このような結果になります。

また、前回紹介した継続タスクも、TaskContinuationOptions.AttachedToParentフラグを指定することで子タスクにすることができます。

相違点

MSDNライブラリの記事に基本的な相違点についてまとめられていますので抜粋します。この表にもあるように、親子関係を作成する方が幾分よいサポートが受けられそうですが、実際にはより自由度の高い入れ子タスクを利用して、開発者が独自に管理することが多いのではないかと推測します。

内容 入れ子 親子
外側のタスクは内側のタスクの完了を待機するか しない する
親は子によってスローされた例外を反映するか しない する
親の状態は、子の状態に依存するか しない する

次回予告

今回はタスク内でのタスク生成と、タスクに親子関係を持たせる方法について見てきました。次回はタスク内で発生する例外の取り扱いについて見ていきたいと思います。