다음을 통해 공유


연결된 자식 작업 및 분리된 자식 작업

자식 작업(또는 중첩된 작업)은 System.Threading.Tasks.Task부모 작업이라고 하는 다른 작업의 사용자 대리자에서 만들어진 인스턴스입니다. 자식 작업은 분리되거나 연결될 수 있습니다. 분리된 자식 작업은 부모와 독립적으로 실행되는 작업입니다. 연결된 자식 작업은 부모가 명시적으로 또는 기본적으로 연결을 금지하지 않는 TaskCreationOptions.AttachedToParent 옵션으로 만든 중첩된 작업입니다. 태스크는 시스템 리소스에 의해서만 제한되는 연결된 자식 작업 및 분리된 자식 작업의 수를 만들 수 있습니다.

다음 표에서는 두 종류의 자식 작업 간의 기본 차이점을 나열합니다.

카테고리 분리된 자식 작업 연결된 자식 작업
부모는 자식 작업이 완료되기를 기다립니다. 아니오
부모는 자식 작업에서 던져진 예외를 전파합니다. 아니오
부모의 상태는 자식의 상태에 따라 달라집니다. 아니오

대부분의 시나리오에서는 다른 작업과의 관계가 덜 복잡하기 때문에 분리된 자식 작업을 사용하는 것이 좋습니다. 따라서 부모 작업 내에서 만든 작업은 기본적으로 분리되며 연결된 자식 작업을 만드는 옵션을 명시적으로 지정 TaskCreationOptions.AttachedToParent 해야 합니다.

분리된 자식 작업

자식 작업은 부모 태스크에 의해 만들어지지만 기본적으로 부모 작업과는 독립적입니다. 다음 예제에서 부모 작업은 간단한 자식 작업 하나를 만듭니다. 예제 코드를 여러 번 실행하는 경우 예제의 출력이 표시된 것과 다르며 코드를 실행할 때마다 출력이 변경되는 것을 알 수 있습니다. 이는 부모 작업과 자식 작업이 서로 독립적으로 실행되므로 발생합니다. 자식은 분리된 작업입니다. 이 예제에서는 부모 작업이 완료되기를 기다리는 경우 콘솔 앱이 종료되기 전에 자식 작업이 실행되거나 완료되지 않을 수 있습니다.

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

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

            Task 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 속성에 액세스하여 부모 작업이 자식이 완료될 때까지 대기하도록 할 수 있습니다. Result 속성은 작업이 완료될 때까지 차단되며, 이는 다음 예제에 나와 있습니다.

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

class Example3
{
    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 {outer.Result}.");
    }
}

// The example displays the following output:
//       Outer task executing.
//       Nested task starting.
//       Nested task completing.
//       Outer has returned 42.
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

연결된 자식 작업

분리된 자식 작업과 달리 연결된 자식 작업은 부모와 밀접하게 동기화됩니다. 다음 예제와 같이, 작업 생성 구문에서 TaskCreationOptions.AttachedToParent 옵션을 사용하여 이전 예제의 분리된 자식 작업을 연결된 자식 작업으로 변경할 수 있습니다. 이 코드에서는 연결된 자식 작업이 부모 작업보다 먼저 완료됩니다. 결과적으로 예제의 출력은 코드를 실행할 때마다 동일합니다.

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.

연결된 자식 작업을 사용하여 비동기 작업의 긴밀하게 동기화된 그래프를 만들 수 있습니다.

그러나 자식 작업은 부모가 연결된 자식 작업을 금지하지 않는 경우에만 부모에 연결할 수 있습니다. 부모 태스크는 부모 태스크의 클래스 생성자 또는 메서드에서 TaskCreationOptions.DenyChildAttach 옵션을 지정하여 자식 태스크가 해당 태스크에 연결되지 않도록 명시적으로 방지할 수 있습니다. 부모 태스크는 메서드 Task.Run를 호출하여 자식 태스크가 연결되지 않도록 암묵적으로 방지합니다. 다음 예제에서는 이를 보여 줍니다. 이전 예제와 동일하지만, Task.Run(Action) 메서드 대신 TaskFactory.StartNew(Action) 메서드를 호출하여 부모 작업을 생성한다는 점에서 차이가 있습니다. 자식 작업이 부모에 연결할 수 없으므로 예제의 출력을 예측할 수 없습니다. "기본 작업 생성 옵션에 Task.Run이 포함되어 있으므로 TaskCreationOptions.DenyChildAttach 오버로드에 대한 이 예제는 '분리된 자식 작업' 섹션의 첫 번째 예제와 기능적으로 동일합니다."

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

public class Example2
{
   public static void Main()
   {
      var parent = Task.Run(() => {
            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 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.

자식 작업의 예외

분리된 자식 태스크에서 예외를 발생시키는 경우, 해당 예외는 중첩되지 않은 태스크와 마찬가지로 부모 태스크에서 직접 관찰하거나 처리해야 합니다. 연결된 자식 태스크에서 예외를 throw하면 예외가 자동으로 부모 작업으로 전파되고 태스크의 Task<TResult>.Result 속성에 액세스하려고 하거나 대기하는 스레드로 돌아갑니다. 따라서 연결된 아동 작업을 사용함으로써, 호출 스레드에 대한 Task.Wait 호출 시점에서 모든 예외를 하나의 지점에서만 처리할 수 있습니다. 자세한 내용은 예외 처리참조하세요.

취소 및 자식 작업

작업 취소는 상호 협력이 필요합니다. 즉, 모든 연결되거나 분리된 자식 작업은 취소 가능하도록 취소 토큰의 상태를 모니터링해야 합니다. 하나의 취소 요청을 사용하여 부모와 모든 자식을 취소하려면 동일한 토큰을 모든 작업의 인수로 전달하고, 각 작업에 요청에 응답하는 논리를 구현해야 합니다. 자세한 내용은 작업 취소작업 및 그의 하위 작업 취소 방법을 참조하십시오.

부모가 취소하는 경우

부모가 자식 작업이 시작되기 전에 자체를 중지하면 자식은 시작되지 않습니다. 자식 작업이 이미 시작된 후, 부모 작업이 스스로를 취소하는 경우에도, 자식 작업은 자체 취소 논리가 없는 한 완료됩니다. 자세한 내용은 작업 취소참조하세요.

분리된 자식 작업이 취소되는 경우

부모에 전달된 것과 동일한 토큰을 사용하여 분리된 자식 작업이 자체적으로 취소되고 부모가 자식 작업을 기다리지 않는 경우 예외가 무해한 협력 취소로 처리되기 때문에 예외가 전파되지 않습니다. 이 동작은 최상위 작업의 동작과 동일합니다.

연결된 자식 작업이 취소되는 경우

연결된 자식 작업이 부모 작업에 전달된 것과 동일한 토큰을 사용하여 스스로 취소되면, TaskCanceledExceptionAggregateException 내의 조인된 스레드로 전달됩니다. 연결된 자식 작업의 그래프를 통해 전파되는 모든 오류 예외 외에도 모든 무해한 예외를 처리할 수 있도록 부모 작업을 기다려야 합니다.

자세한 내용은 예외 처리참조하세요.

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

자식 태스크에 의해 throw되는 처리되지 않은 예외가 부모 태스크로 전파됩니다. 이 동작을 사용하여 작업 트리를 트래버스하는 대신 하나의 루트 작업에서 모든 자식 작업 예외를 관찰할 수 있습니다. 그러나 부모 작업에서 다른 코드의 첨부 파일을 기대하지 않는 경우 예외 전파에 문제가 있을 수 있습니다. 예를 들어, Task 객체를 사용하여 타사 라이브러리 구성 요소를 호출하는 앱을 고려해 보세요. 타사 라이브러리 구성 요소도 개체를 Task 만들고 부모 작업에 연결하도록 지정 TaskCreationOptions.AttachedToParent 하는 경우 자식 작업에서 발생하는 처리되지 않은 예외가 부모로 전파됩니다. 이로 인해 주 앱에서 예기치 않은 동작이 발생할 수 있습니다.

자식 작업이 부모 작업에 연결되지 않도록 하려면 부모 TaskCreationOptions.DenyChildAttach 또는 Task 개체를 Task<TResult> 만들 때 옵션을 지정합니다. 태스크가 부모에 연결하려고 할 때 부모가 옵션 TaskCreationOptions.DenyChildAttach을 지정하면, 자식 태스크는 부모에 연결할 수 없으며, 옵션 TaskCreationOptions.AttachedToParent이 지정되지 않은 것처럼 실행됩니다.

자식 작업이 적시에 완료되지 않을 경우 자식 작업이 부모 작업에 연결되지 않도록 방지할 수도 있습니다. 부모 작업은 모든 자식 작업이 완료될 때까지 완료되지 않으므로 장기 실행 자식 작업으로 인해 전체 앱의 성능이 저하될 수 있습니다. 작업이 부모 작업에 연결되지 않도록 하여 앱 성능을 향상시키는 방법을 보여 주는 예제는 방법: 자식 작업이 부모에 연결되지 않도록 하는 방법을 참조하세요.

참고하십시오