방법: 자식 작업이 부모 작업에 연결되지 않도록 방지

이 문서에서는 자식 작업이 부모 작업에 연결하는 것을 방지하는 방법을 보여 줍니다. 자식 작업이 부모에 연결하는 것을 방지하는 방법은 타사가 작성하고 마찬가지로 작업을 사용하는 구성 요소를 호출하는 경우 유용합니다. 예를 들어 TaskCreationOptions.AttachedToParent 옵션을 사용하여 Task 또는 Task<TResult> 개체를 만드는 타사 구성 요소는 오래 실행되는 경우 코드에서 문제를 유발하거나 처리되지 않은 예외를 throw할 수 있습니다.

예시

다음 예제에서는 기본 옵션의 효과와 자식 작업이 부모에 연결하는 것을 방지하는 효과를 비교합니다. 이 예제에서는 마찬가지로 Task 개체를 사용하는 타사 라이브러리를 호출하는 Task 개체를 만듭니다. 타사 라이브러리는 AttachedToParent 옵션을 사용하여 Task 개체를 만듭니다. 애플리케이션은 TaskCreationOptions.DenyChildAttach 옵션을 사용하여 부모 작업을 만듭니다. 이 옵션은 자식 작업에서 AttachedToParent 지정을 제거하도록 런타임에 지시합니다.

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

// Defines functionality that is provided by a third-party.
// In a real-world scenario, this would likely be provided
// in a separate code file or assembly.
namespace Contoso
{
   public class Widget
   {
      public Task Run()
      {
         // Create a long-running task that is attached to the
         // parent in the task hierarchy.
         return Task.Factory.StartNew(() =>
         {
            // Simulate a lengthy operation.
            Thread.Sleep(5000);
         }, TaskCreationOptions.AttachedToParent);
      }
   }
}

// Demonstrates how to prevent a child task from attaching to the parent.
class DenyChildAttach
{
   static void RunWidget(Contoso.Widget widget,
      TaskCreationOptions parentTaskOptions)
   {
      // Record the time required to run the parent
      // and child tasks.
      Stopwatch stopwatch = new Stopwatch();
      stopwatch.Start();

      Console.WriteLine("Starting widget as a background task...");

      // Run the widget task in the background.
      Task<Task> runWidget = Task.Factory.StartNew(() =>
         {
            Task widgetTask = widget.Run();

            // Perform other work while the task runs...
            Thread.Sleep(1000);

            return widgetTask;
         }, parentTaskOptions);

      // Wait for the parent task to finish.
      Console.WriteLine("Waiting for parent task to finish...");
      runWidget.Wait();
      Console.WriteLine("Parent task has finished. Elapsed time is {0} ms.",
         stopwatch.ElapsedMilliseconds);

      // Perform more work...
      Console.WriteLine("Performing more work on the main thread...");
      Thread.Sleep(2000);
      Console.WriteLine("Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds);

      // Wait for the child task to finish.
      Console.WriteLine("Waiting for child task to finish...");
      runWidget.Result.Wait();
      Console.WriteLine("Child task has finished. Elapsed time is {0} ms.",
        stopwatch.ElapsedMilliseconds);
   }

   static void Main(string[] args)
   {
      Contoso.Widget w = new Contoso.Widget();

      // Perform the same operation two times. The first time, the operation
      // is performed by using the default task creation options. The second
      // time, the operation is performed by using the DenyChildAttach option
      // in the parent task.

      Console.WriteLine("Demonstrating parent/child tasks with default options...");
      RunWidget(w, TaskCreationOptions.None);

      Console.WriteLine();

      Console.WriteLine("Demonstrating parent/child tasks with the DenyChildAttach option...");
      RunWidget(w, TaskCreationOptions.DenyChildAttach);
   }
}

/* Sample output:
Demonstrating parent/child tasks with default options...
Starting widget as a background task...
Waiting for parent task to finish...
Parent task has finished. Elapsed time is 5014 ms.
Performing more work on the main thread...
Elapsed time is 7019 ms.
Waiting for child task to finish...
Child task has finished. Elapsed time is 7019 ms.

Demonstrating parent/child tasks with the DenyChildAttach option...
Starting widget as a background task...
Waiting for parent task to finish...
Parent task has finished. Elapsed time is 1007 ms.
Performing more work on the main thread...
Elapsed time is 3015 ms.
Waiting for child task to finish...
Child task has finished. Elapsed time is 5015 ms.
*/
Imports System.Diagnostics
Imports System.Threading
Imports System.Threading.Tasks

' Defines functionality that is provided by a third-party.
' In a real-world scenario, this would likely be provided
' in a separate code file or assembly.
Namespace Contoso
    Public Class Widget
        Public Function Run() As Task
            ' Create a long-running task that is attached to the 
            ' parent in the task hierarchy.
            Return Task.Factory.StartNew(Sub() Thread.Sleep(5000), TaskCreationOptions.AttachedToParent)
            ' Simulate a lengthy operation.
        End Function
    End Class
End Namespace

' Demonstrates how to prevent a child task from attaching to the parent.
Friend Class DenyChildAttach
    Private Shared Sub RunWidget(ByVal widget As Contoso.Widget, ByVal parentTaskOptions As TaskCreationOptions)
        ' Record the time required to run the parent
        ' and child tasks.
        Dim stopwatch As New Stopwatch()
        stopwatch.Start()

        Console.WriteLine("Starting widget as a background task...")

        ' Run the widget task in the background.
        Dim runWidget As Task(Of Task) = Task.Factory.StartNew(Function()
                                                                   ' Perform other work while the task runs...
                                                                   Dim widgetTask As Task = widget.Run()
                                                                   Thread.Sleep(1000)
                                                                   Return widgetTask
                                                               End Function, parentTaskOptions)

        ' Wait for the parent task to finish.
        Console.WriteLine("Waiting for parent task to finish...")
        runWidget.Wait()
        Console.WriteLine("Parent task has finished. Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)

        ' Perform more work...
        Console.WriteLine("Performing more work on the main thread...")
        Thread.Sleep(2000)
        Console.WriteLine("Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)

        ' Wait for the child task to finish.
        Console.WriteLine("Waiting for child task to finish...")
        runWidget.Result.Wait()
        Console.WriteLine("Child task has finished. Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)
    End Sub

    Shared Sub Main(ByVal args() As String)
        Dim w As New Contoso.Widget()

        ' Perform the same operation two times. The first time, the operation
        ' is performed by using the default task creation options. The second
        ' time, the operation is performed by using the DenyChildAttach option
        ' in the parent task.

        Console.WriteLine("Demonstrating parent/child tasks with default options...")
        RunWidget(w, TaskCreationOptions.None)

        Console.WriteLine()

        Console.WriteLine("Demonstrating parent/child tasks with the DenyChildAttach option...")
        RunWidget(w, TaskCreationOptions.DenyChildAttach)
    End Sub
End Class

' Sample output:
'Demonstrating parent/child tasks with default options...
'Starting widget as a background task...
'Waiting for parent task to finish...
'Parent task has finished. Elapsed time is 5014 ms.
'Performing more work on the main thread...
'Elapsed time is 7019 ms.
'Waiting for child task to finish...
'Child task has finished. Elapsed time is 7019 ms.
'
'Demonstrating parent/child tasks with the DenyChildAttach option...
'Starting widget as a background task...
'Waiting for parent task to finish...
'Parent task has finished. Elapsed time is 1007 ms.
'Performing more work on the main thread...
'Elapsed time is 3015 ms.
'Waiting for child task to finish...
'Child task has finished. Elapsed time is 5015 ms.
'

부모 작업은 자식 작업이 모두 완료될 때까지 완료되지 않기 때문에 오래 실행되는 자식 작업은 전체적인 애플리케이션의 성능을 저하시킬 수 있습니다. 이 예제에서 애플리케이션이 기본 옵션을 사용하여 부모 작업을 만들 때 자식 작업은 부모 작업이 완료되기 전에 완료되어야 합니다. 애플리케이션에서 TaskCreationOptions.DenyChildAttach 옵션을 사용하는 경우 자식은 부모에 연결되지 않습니다. 따라서 애플리케이션은 부모 작업이 완료된 후와 자식 작업이 완료되기를 기다려야 하기 전에 추가 작업을 수행할 수 있습니다.

참고 항목