アタッチされた子タスクとデタッチされた子タスクAttached and Detached Child Tasks

子タスク (または入れ子のタスク) は、親タスク と呼ばれる、別のタスクのユーザー デリゲートで作成された、System.Threading.Tasks.Task のインスタンスです。A child task (or nested task) is a System.Threading.Tasks.Task instance that is created in the user delegate of another task, which is known as the parent task. 子タスクはデタッチまたはアタッチできます。A child task can be either detached or attached. デタッチされた子タスク は、親とは独立して実行されるタスクです。A detached child task is a task that executes independently of its parent. アタッチされた子タスク は、TaskCreationOptions.AttachedToParent オプションで作成される入れ子のタスクです。その親は、明示的にも既定でも、子タスクがアタッチされることを禁止しません。An attached child task is a nested task that is created with the TaskCreationOptions.AttachedToParent option whose parent does not explicitly or by default prohibit it from being attached. タスクでは、システム リソースが許す限り、任意の数のアタッチされた子タスクおよびデタッチされた子タスクを作成できます。A task may create any number of attached and detached child tasks, limited only by system resources.

以下の表に、2 種類の子タスクの基本的な相違点を示します。The following table lists the basic differences between the two kinds of child tasks.

カテゴリCategory デタッチされた子タスクDetached child tasks アタッチされた子タスクAttached child tasks
親は子タスクが完了するまで待機します。Parent waits for child tasks to complete. ×No はいYes
親は子タスクによってスローされた例外を反映します。Parent propagates exceptions thrown by child tasks. ×No はいYes
親のステータスは子のステータスに依存します。Status of parent depends on status of child. ×No はいYes

ほとんどの場合、デタッチされた子タスクを使用することをお勧めします。他のタスクとの関係は複雑度が低いためです。In most scenarios, we recommend that you use detached child tasks, because their relationships with other tasks are less complex. こうした理由から、既定では親タスク内に作成されたタスクはデタッチされており、アタッチされた子タスクを作成する場合は TaskCreationOptions.AttachedToParent オプションを明示的に指定する必要があります。That is why tasks created inside parent tasks are detached by default, and you must explicitly specify the TaskCreationOptions.AttachedToParent option to create an attached child task.

デタッチされた子タスクDetached child tasks

子タスクは親タスクによって作成されますが、既定では親タスクに依存しません。Although a child task is created by a parent task, by default it is independent of the parent task. 次の例では、親タスクが単に 1 つの子タスクを作成します。In the following example, a parent task creates one simple child task. このコード例を複数回実行すると、出力がここに示したものとは異なり、またコードを実行するたびに出力が変わる場合があることに気付くことがあります。If you run the example code multiple times, you may notice that the output from the example differs from that shown, and also that the output may change each time you run the code. これは親タスクと子タスクが、それぞれ独立して実行されるために生じます。子タスクはデタッチされたタスクです。This occurs because the parent task and child tasks execute independently of each other; the child is a detached task. この例は親タスクの完了のみを待機します。コンソール アプリが終了する前には、子タスクは実行または完了しないことがあります。The example waits only for the parent task to complete, and the child task may not execute or complete before the console app terminates.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
         Console.WriteLine("Outer task executing.");

         var child = Task.Factory.StartNew(() => {
            Console.WriteLine("Nested task starting.");
            Thread.SpinWait(500000);
            Console.WriteLine("Nested task completing.");
         });
      });

      parent.Wait();
      Console.WriteLine("Outer has completed.");
   }
}
// The example produces output like the following:
//        Outer task executing.
//        Nested task starting.
//        Outer has completed.
//        Nested task completing.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim parent = Task.Factory.StartNew(Sub()
                                            Console.WriteLine("Outer task executing.")
                                            Dim child = Task.Factory.StartNew(Sub()
                                                                                 Console.WriteLine("Nested task starting.")
                                                                                 Thread.SpinWait(500000)
                                                                                 Console.WriteLine("Nested task completing.")
                                                                              End Sub)
                                         End Sub)
      parent.Wait()
      Console.WriteLine("Outer task has completed.")
    End Sub
End Module
' The example produces output like the following:
'   Outer task executing.
'   Nested task starting.
'   Outer task has completed.
'   Nested task completing.

子タスクが Task<TResult> オブジェクトではなく Task オブジェクトによって表される場合、デタッチされた子タスクであっても、子の Task<TResult>.Result プロパティにアクセスして、親タスクが子タスクの完了を待機することを確認できます。If the child task is represented by a Task<TResult> object rather than a Task object, you can ensure that the parent task will wait for the child to complete by accessing the Task<TResult>.Result property of the child even if it is a detached child task. 次の例に示すように、Result プロパティはタスクが完了するまでブロックします。The Result property blocks until its task completes, as the following example shows.

using System;
using System.Threading;
using System.Threading.Tasks;

class Example
{
   static void Main()
   {
      var outer = Task<int>.Factory.StartNew(() => {
            Console.WriteLine("Outer task executing.");

            var nested = Task<int>.Factory.StartNew(() => {
                  Console.WriteLine("Nested task starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Nested task completing.");
                  return 42;
            });

            // Parent will wait for this detached child.
            return nested.Result;
      });

      Console.WriteLine("Outer has returned {0}.", outer.Result);
   }
}
// The example displays the following output:
//       Outer task executing.
//       Nested task starting.
//       Nested task completing.
//       Outer has returned 42.
Imports System
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim parent = Task(Of Integer).Factory.StartNew(Function()
                                                        Console.WriteLine("Outer task executing.")
                                                        Dim child = Task(Of Integer).Factory.StartNew(Function()
                                                                                                         Console.WriteLine("Nested task starting.")
                                                                                                         Thread.SpinWait(5000000)
                                                                                                         Console.WriteLine("Nested task completing.")
                                                                                                         Return 42
                                                                                                      End Function)
                                                        Return child.Result


                                                     End Function)
      Console.WriteLine("Outer has returned {0}", parent.Result)
   End Sub
End Module      
' The example displays the following output:
'       Outer task executing.
'       Nested task starting.
'       Detached task completing.
'       Outer has returned 42

アタッチされた子タスクAttached child tasks

デタッチされた子タスクとは異なり、アタッチされた子タスクは親と緊密に同期します。Unlike detached child tasks, attached child tasks are closely synchronized with the parent. 次の例に示すように、タスクの作成ステートメントで TaskCreationOptions.AttachedToParent オプションを使用すると、前の例のデタッチされた子タスクを、アタッチされた子タスクに変更できます。You can change the detached child task in the previous example to an attached child task by using the TaskCreationOptions.AttachedToParent option in the task creation statement, as shown in the following example. このコードでは、アタッチされた子タスクは親の前に完了します。In this code, the attached child task completes before its parent. その結果、この例の出力結果は、コードを実行するたびに同じになります。As a result, the output from the example is the same each time you run the code.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
            Console.WriteLine("Parent task executing.");
            var child = Task.Factory.StartNew(() => {
                  Console.WriteLine("Attached child starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Attached child completing.");
            }, TaskCreationOptions.AttachedToParent);
      });
      parent.Wait();
      Console.WriteLine("Parent has completed.");
   }
}
// The example displays the following output:
//       Parent task executing.
//       Attached child starting.
//       Attached child completing.
//       Parent has completed.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim parent = Task.Factory.StartNew(Sub()
                                            Console.WriteLine("Parent task executing")
                                            Dim child = Task.Factory.StartNew(Sub()
                                                                                 Console.WriteLine("Attached child starting.")
                                                                                 Thread.SpinWait(5000000)
                                                                                 Console.WriteLine("Attached child completing.")
                                                                              End Sub, TaskCreationOptions.AttachedToParent)
                                         End Sub)
      parent.Wait()
      Console.WriteLine("Parent has completed.")
   End Sub
End Module
' The example displays the following output:
'       Parent task executing.
'       Attached child starting.
'       Attached child completing.
'       Parent has completed.

アタッチされた子タスクを使用すると、非同期操作の厳密に同期されたグラフを作成できます。You can use attached child tasks to create tightly synchronized graphs of asynchronous operations.

ただし、その親タスクが子タスクのアタッチを禁止していない場合にのみ、子タスクは親タスクにアタッチできます。However, a child task can attach to its parent only if its parent does not prohibit attached child tasks. 親タスクは、親タスクのクラスのコンストラクターの TaskCreationOptions.DenyChildAttach オプションまたは TaskFactory.StartNew メソッドで指定することで、明示的に子タスクが親タスクにアタッチできないようにすることができます。Parent tasks can explicitly prevent child tasks from attaching to them by specifying the TaskCreationOptions.DenyChildAttach option in the parent task's class constructor or the TaskFactory.StartNew method. 親タスクが Task.Run メソッドを呼び出して作成された場合、親タスクは暗黙的に子タスクをアタッチできないようにします。Parent tasks implicitly prevent child tasks from attaching to them if they are created by calling the Task.Run method. 次に例を示します。The following example illustrates this. 親タスクが TaskFactory.StartNew(Action) メソッドではなく Task.Run(Action) メソッドを呼び出して作成される点を除き、これは前の例と同一です。It is identical to the previous example, except that the parent task is created by calling the Task.Run(Action) method rather than the TaskFactory.StartNew(Action) method. 子タスクはその親にアタッチすることができないため、例からの出力は予測できません。Because the child task is not able to attach to its parent, the output from the example is unpredictable. Task.Run のオーバーロードにおける既定のタスクの作成オプションには TaskCreationOptions.DenyChildAttach が含まれるため、この例は、「デタッチされた子タスク」セクションの最初の例と機能的に同等です。Because the default task creation options for the Task.Run overloads include TaskCreationOptions.DenyChildAttach, this example is functionally equivalent to the first example in the "Detached child tasks" section.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var parent = Task.Run(() => {
            Console.WriteLine("Parent task executing.");
            var child = Task.Factory.StartNew(() => {
                  Console.WriteLine("Child starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Child completing.");
            }, TaskCreationOptions.AttachedToParent);
      });
      parent.Wait();
      Console.WriteLine("Parent has completed.");
   }
}
// The example displays output like the following:
//       Parent task executing
//       Parent has completed.
//       Attached child starting.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim parent = Task.Run(Sub()
                               Console.WriteLine("Parent task executing")
                               Dim child = Task.Factory.StartNew(Sub()
                                              Console.WriteLine("Attached child starting.")
                                              Thread.SpinWait(5000000)
                                              Console.WriteLine("Attached child completing.")
                                           End Sub, TaskCreationOptions.AttachedToParent)
                            End Sub)
      parent.Wait()
      Console.WriteLine("Parent has completed.")
   End Sub
End Module
' The example displays output like the following:
'       Parent task executing
'       Parent has completed.
'       Attached child starting.

子タスクでの例外Exceptions in child tasks

デタッチされた子タスクが例外をスローする場合、その例外は入れ子でないタスクの場合と同様に監視するか、または親タスク内で直接処理する必要があります。If a detached child task throws an exception, that exception must be observed or handled directly in the parent task just as with any non-nested task. アタッチされた子タスクが例外をスローした場合、例外は自動的に親タスクに反映され、タスクの Task<TResult>.Result プロパティへのアクセスを待機するか、アクセスを試みるスレッドに戻されます。If an attached child task throws an exception, the exception is automatically propagated to the parent task and back to the thread that waits or tries to access the task's Task<TResult>.Result property. したがって、アタッチされた子タスクを使用することで、呼び出し元のスレッドの Task.Wait の呼び出しの 1 つの場所ですべての例外を処理できます。Therefore, by using attached child tasks, you can handle all exceptions at just one point in the call to Task.Wait on the calling thread. 詳細については、「例外処理」を参照してください。For more information, see Exception Handling.

キャンセルと子タスクCancellation and child tasks

タスクの取り消し処理は他の処理と連携して行われます。Task cancellation is cooperative. つまり、キャンセル可能であるためには、すべてのアタッチされた子タスク、またはデタッチされた子タスクが、キャンセル トークンの状態を監視する必要があります。That is, to be cancelable, every attached or detached child task must monitor the status of the cancellation token. 1 つのキャンセル要求を使用して親とその子をすべて取り消す場合は、同じトークンをすべてのタスクに引数として渡し、各タスクの要求に応答するためのロジックを各タスクに提供します。If you want to cancel a parent and all its children by using one cancellation request, you pass the same token as an argument to all tasks and provide in each task the logic to respond to the request in each task. 詳細については、「タスクのキャンセル」と「方法: タスクとその子を取り消す」を参照してください。For more information, see Task Cancellation and How to: Cancel a Task and Its Children.

親が取り消された場合When the parent cancels

子タスクが開始される前に親が取り消された場合、子は開始されません。If a parent cancels itself before its child task is started, the child never starts. 子タスクが既に開始された後に親が取り消された場合、子はそれ自体にキャンセル ロジックが適用されていない限り、完了まで実行されます。If a parent cancels itself after its child task has already started, the child runs to completion unless it has its own cancellation logic. 詳細については、「タスクのキャンセル」をご覧ください。For more information, see Task Cancellation.

デタッチされた子タスクが取り消された場合When a detached child task cancels

デタッチされた子タスクが、そのタ親に渡されたのと同じトークンを使用して取り消された場合、親は子タスクを待機せず、例外も反映されません。例外は、他の処理と連携したキャセル処理として扱われるためです。If a detached child task cancels itself by using the same token that was passed to the parent, and the parent does not wait for the child task, no exception is propagated, because the exception is treated as benign cooperation cancellation. この動作は最上位のタスクと同じです。This behavior is the same as that of any top-level task.

アタッチされた子タスクが取り消された場合When an attached child task cancels

アタッチされた子タスクが、その親タスクに渡されたのと同じトークンを使用して取り消された場合、TaskCanceledExceptionAggregateException 内の連結されたスレッドに反映されます。When an attached child task cancels itself by using the same token that was passed to its parent task, a TaskCanceledException is propagated to the joining thread inside an AggregateException. アタッチされた子タスクのグラフにまで反映されるすべてのエラーが発生している例外に加え、問題のないすべての例外も処理できるようにするため、親タスクを待機する必要があります。You must wait for the parent task so that you can handle all benign exceptions in addition to all faulting exceptions that are propagated up through a graph of attached child tasks.

詳細については、「例外処理」を参照してください。For more information, see Exception Handling.

子タスクがその親にアタッチされないようにするPreventing a child task from attaching to its parent

子タスクがスローした未処理の例外は親タスクに反映されます。An unhandled exception that is thrown by a child task is propagated to the parent task. この動作を使うと、タスク ツリーを走査することなく、1 つのルート タスクのすべての子タスクの例外を確認することができます。You can use this behavior to observe all child task exceptions from one root task instead of traversing a tree of tasks. ただし、親タスクは他のコードからのアタッチを想定していない場合には、例外の反映は問題となる場合があります。However, exception propagation can be problematic when a parent task does not expect attachment from other code. たとえば、Task オブジェクトのサードパーティ ライブラリのコンポーネントを呼び出すアプリケーションを考えてみます。For example, consider an app that calls a third-party library component from a Task object. サードパーティのライブラリのコンポーネントが Task オブジェクトを作成し、親タスクにアタッチするように TaskCreationOptions.AttachedToParent を指定する場合は、子タスクで発生するハンドルされない例外はすべて親に反映されます。If the third-party library component also creates a Task object and specifies TaskCreationOptions.AttachedToParent to attach it to the parent task, any unhandled exceptions that occur in the child task propagate to the parent. これによりメイン アプリケーションで予期しない動作が発生することがあります。This could lead to unexpected behavior in the main app.

子タスクが親タスクにアタッチされないようにするには、親の TaskCreationOptions.DenyChildAttach または Task オブジェクトを作成するときに、Task<TResult> オプションを指定します。To prevent a child task from attaching to its parent task, specify the TaskCreationOptions.DenyChildAttach option when you create the parent Task or Task<TResult> object. タスクがその親にアタッチしようとし、親が TaskCreationOptions.DenyChildAttach オプションを指定する場合、子タスクは親にアタッチされず、TaskCreationOptions.AttachedToParent オプションが指定されなかったかのように実行されます。When a task tries to attach to its parent and the parent specifies the TaskCreationOptions.DenyChildAttach option, the child task will not be able to attach to a parent and will execute just as if the TaskCreationOptions.AttachedToParent option was not specified.

子タスクが適時に完了しない場合には、子タスクがその親にアタッチしないようにすることをお勧めします。You might also want to prevent a child task from attaching to its parent when the child task does not finish in a timely manner. 親タスクは、すべての子タスクが終了するまで完了しないため、長時間実行される子タスクによって、アプリケーション全体のパフォーマンスの低下を生じる場合があります。Because a parent task does not finish until all child tasks finish, a long-running child task can cause the overall app to perform poorly. タスクがその親タスクにアタッチしないようにすることにより、アプリケーションのパフォーマンスを向上させる方法の例については、「方法: 子タスクがその親にアタッチしないようにする」を参照してください。For an example that shows how to improve app performance by preventing a task from attaching to its parent task, see How to: Prevent a Child Task from Attaching to its Parent.

関連項目See also