다음을 통해 공유


연속 작업

업데이트: 2010년 6월

비동기 프로그래밍에서는 하나의 비동기 작업에서 해당 작업이 완료될 때 두 번째 작업을 호출하고 이 작업에 데이터를 전달하는 것이 매우 일반적입니다. 일반적으로 이 과정은 콜백 메서드를 사용하여 수행되었습니다. 작업 병렬 라이브러리에서는 연속 작업을 통해 동일한 기능이 제공됩니다. 연속 작업은 선행 작업이라고 하는 다른 작업이 완료될 때 선행 작업에 의해 호출되는 비동기 작업입니다.

연속 작업은 비교적 사용하기 쉬우면서도 매우 강력하고 유연한 기능입니다. 예를 들어, 다음을 수행할 수 있습니다.

  • 선행 작업의 데이터를 연속 작업에 전달할 수 있습니다.

  • 연속 작업이 호출되거나 호출되지 않을 정확한 조건을 지정할 수 있습니다.

  • 연속 작업을 해당 작업이 시작되기 전에 취소하거나 해당 작업이 실행 중일 경우 협조적으로 취소할 수 있습니다.

  • 연속 작업을 예약하는 방법에 대한 힌트를 제공할 수 있습니다.

  • 한 선행 작업에서 여러 연속 작업을 호출할 수 있습니다.

  • 여러 선행 작업이 모두 또는 하나라도 완료될 때 하나의 연속 작업을 호출할 수 있습니다.

  • 길이에 관계없이 여러 연속 작업을 차례로 체인할 수 있습니다.

  • 연속 작업을 사용하여 선행 작업에서 throw된 예외를 처리할 수 있습니다.

Task.ContinueWith 메서드를 사용하여 연속 작업을 만들 수 있습니다. 다음 예제에서는 기본적인 패턴을 보여 줍니다. 혼동을 줄이기 위해 예외 처리는 생략되었습니다.

' The antecedent task. Can also be created with Task.Factory.StartNew.
Dim taskA As Task(Of DayOfWeek) = New Task(Of DayOfWeek)(Function()
                                                             Return DateTime.Today.DayOfWeek
                                                         End Function)
' The continuation. Its delegate takes the antecedent task
' as an argument and can return a different type.
Dim continuation As Task(Of String) = taskA.ContinueWith(Function(antecedent)
                                                             Return String.Format("Today is {0}", antecedent.Result)
                                                         End Function)
' Start the antecedent.
taskA.Start()

' Use the contuation's result.
Console.WriteLine(continuation.Result)
            // The antecedent task. Can also be created with Task.Factory.StartNew.
            Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);

            // The continuation. Its delegate takes the antecedent task
            // as an argument and can return a different type.
            Task<string> continuation = taskA.ContinueWith((antecedent) =>
                {
                    return String.Format("Today is {0}.",
                                        antecedent.Result);
                });

            // Start the antecedent.
            taskA.Start();

            // Use the contuation's result.
            Console.WriteLine(continuation.Result);

다음 예제와 같이 작업 배열의 작업이 하나라도 완료되거나 모두 완료될 때 실행되는 여러 작업으로 구성된 연속 작업을 만들 수도 있습니다.

Dim task1 As Task(Of Integer) = New Task(Of Integer)(Function()
                                                         ' Do some work...
                                                         Return 34
                                                     End Function)

Dim task2 As Task(Of Integer) = New Task(Of Integer)(Function()
                                                         ' Do some work...
                                                         Return 8
                                                     End Function)

Dim tasks() As Task(Of Integer) = {task1, task2}

Dim continuation = Task.Factory.ContinueWhenAll(tasks, Sub(antecedents)
                                                           Dim answer As Integer = tasks(0).Result + tasks(1).Result
                                                           Console.WriteLine("The answer is {0}", answer)


                                                       End Sub)
task1.Start()
task2.Start()
continuation.Wait()
            Task<int>[] tasks = new Task<int>[2];
            tasks[0] = new Task<int>(() =>
            {
                // Do some work... 
                return 34;
            });

            tasks[1] = new Task<int>(() =>
            {
                // Do some work...
                 return 8;
            });

            var continuation = Task.Factory.ContinueWhenAll(
                            tasks,
                            (antecedents) =>
                            {
                                int answer = tasks[0].Result + tasks[1].Result;
                                Console.WriteLine("The answer is {0}", answer);
                            });

            tasks[0].Start();
            tasks[1].Start();
            continuation.Wait();

연속 작업은 WaitingForActivation 상태로 만들어지므로 선행 작업에 의해서만 시작될 수 있습니다. 사용자 코드에서 연속 작업에 대한 Task.Start를 호출하면 System.InvalidOperationException이 발생합니다.

연속 작업 자체는 Task이며 연속 작업이 시작된 스레드를 차단하지 않습니다. 연속 작업이 완료될 때까지 차단하려면 Wait 메서드를 사용합니다.

연속 작업 옵션

단일 작업으로 구성된 연속 작업을 만들 때는 System.Threading.Tasks.TaskContinuationOptions 열거형을 사용하여 선행 작업이 연속 작업을 시작해야 하는 조건을 지정하는 ContinueWith 오버로드를 사용할 수 있습니다. 예를 들어 선행 작업의 실행이 완료되었거나 Faulted 상태로 완료된 경우에만 연속 작업이 실행되도록 지정할 수 있습니다. 선행 작업에서 연속 작업을 호출할 준비가 되었지만 조건이 true가 아니면 연속 작업은 바로 Canceled 상태로 전환되며 이후에는 해당 작업을 시작할 수 없습니다. 여러 작업으로 구성된 연속 작업에 NotOn 또는 OnlyOn 옵션을 지정하면 런타임에 예외가 throw됩니다.

System.Threading.Tasks.TaskContinuationOptions 열거형에는 System.Threading.Tasks.TaskCreationOptions 열거형과 동일한 옵션도 포함되어 있습니다. 두 열거형 형식 모두에서 AttachedToParent, LongRunningPreferFairness의 의미와 값은 동일합니다. 이러한 옵션은 여러 작업으로 구성된 연속 작업에 사용할 수 있습니다.

다음 표에서는 TaskContinuationOptions의 모든 값을 보여 줍니다.

요소

설명

None

TaskContinuationOptions가 지정되지 않은 경우의 기본 동작을 지정합니다. 연속 작업은 선행 작업의 최종 상태에 관계없이 선행 작업이 완료되면 예약됩니다. 작업이 자식 작업인 경우 해당 작업은 분리된 상태의 중첩된 작업으로 만들어집니다.

PreferFairness

먼저 예약된 작업은 먼저 실행될 가능성이 높고 나중에 예약된 작업은 나중에 실행될 가능성이 높게 연속 작업이 예약되도록 지정합니다.

LongRunning

연속 작업이 장기 실행되는 성긴 작업이 되도록 지정합니다. 초과 구독을 보장할 수 있는 System.Threading.Tasks.TaskScheduler에 대한 힌트를 제공합니다.

AttachedToParent

연속 작업이 자식 작업인 경우 작업 계층 구조의 부모에 연결되도록 지정합니다. 연속 작업은 선행 작업도 자식 작업인 경우에만 자식 작업이 됩니다.

NotOnRanToCompletion

선행 작업의 실행이 완료된 경우 연속 작업이 예약되지 않도록 지정합니다.

NotOnFaulted

선행 작업이 처리되지 않은 예외를 throw한 경우 연속 작업이 예약되지 않도록 지정합니다.

NotOnCanceled

선행 작업이 취소된 경우 연속 작업이 예약되지 않도록 지정합니다.

OnlyOnRanToCompletion

선행 작업의 실행이 완료된 경우에만 연속 작업이 예약되도록 지정합니다.

OnlyOnFaulted

선행 작업이 처리되지 않은 예외를 throw한 경우에만 연속 작업이 예약되도록 지정합니다. OnlyOnFaulted 옵션을 사용하는 경우 선행 작업의 Exception 속성은 항상 null이 아닙니다. 이 속성을 사용하여 예외를 catch하고 해당 작업에 오류를 발생시킨 예외를 확인할 수 있습니다. Exception 속성에 액세스하지 않는 경우에는 예외가 처리되지 않습니다. 또한 취소되었거나 오류가 발생한 작업의 Result 속성에 액세스하려고 하면 새 예외가 발생합니다.

OnlyOnCanceled

선행 작업이 Canceled 상태로 완료된 경우에만 연속 작업이 예약되도록 지정합니다.

ExecuteSynchronously

매우 단기로 실행되는 연속 작업에 사용되며, 선행 작업이 최종 상태로 전환되도록 하는 스레드와 동일한 스레드에서 연속 작업이 실행되도록 지정합니다. 연속 작업이 만들어질 때 선행 작업이 이미 완료된 경우 시스템에서는 연속 작업을 만드는 스레드에서 연속 작업을 실행하려고 합니다. 선행 작업의 CancellationTokenSource가 finally 블록(Visual Basic에서는 Finally)에서 삭제된 경우 해당 finally 블록에서 이 옵션을 사용하여 연속 작업이 계속 실행됩니다.

연속 작업에 데이터 전달

선행 작업에 대한 참조는 연속 작업의 사용자 대리자에 인수로 전달됩니다. 선행 작업이 System.Threading.Tasks.Task<TResult>이고 이 작업의 실행이 완료된 경우 연속 작업에서는 선행 작업의 Task<TResult>.Result 속성에 액세스할 수 있습니다. 여러 작업으로 구성된 연속 작업과 Task.WaitAll 메서드를 사용할 경우 인수는 선행 작업의 배열입니다. Task.WaitAny를 사용할 경우 인수는 완료된 첫째 선행 작업입니다.

Task<TResult>.Result는 작업이 완료될 때까지 차단됩니다. 그러나 작업이 취소되거나 오류가 발생한 경우 코드에서 Result 속성에 액세스하려고 하면 예외가 throw됩니다. 다음 예제와 같이 OnlyOnRanToCompletion 옵션을 사용하면 이 문제를 방지할 수 있습니다.

            Dim aTask = Task(Of Integer).Factory.StartNew(Function()
                                                              Return 54
                                                          End Function)
            Dim bTask = aTask.ContinueWith(Sub(antecedent)
                                               Console.WriteLine("continuation {0}", antecedent.Result)
                                           End Sub,
                                           TaskContinuationOptions.OnlyOnRanToCompletion)

var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>
{
    Console.WriteLine("continuation {0}", antecedent.Result);
},
    TaskContinuationOptions.OnlyOnRanToCompletion);

선행 작업의 실행이 완료되지 않은 경우에도 연속 작업이 실행되도록 하려면 연속 작업을 예외로부터 보호해야 합니다. 사용 가능한 방법 중 하나는 선행 작업의 상태를 테스트하고 상태가 Faulted 또는 Canceled가 아닌 경우에만 Result에 액세스하는 것입니다. 선행 작업의 Exception 속성을 확인해도 됩니다. 자세한 내용은 예외 처리(작업 병렬 라이브러리)를 참조하십시오.

연속 작업 취소

다음과 같은 경우 연속 작업이 Canceled 상태가 됩니다.

  • 취소 요청에 대한 응답으로 연속 작업에서 OperationCanceledException이 throw되는 경우. 다른 작업과 마찬가지로, 연속 작업에 전달된 것과 동일한 토큰이 예외에 들어 있으면 협조적 취소가 승인된 것으로 처리됩니다.

  • 연속 작업이 실행되기 전에 연속 작업에 System.Threading.CancellationToken이 인수로 전달되었으며 이 토큰의 IsCancellationRequested 속성이 true(True)인 경우. 이 경우 연속 작업은 시작되지 않으며 바로 Canceled 상태로 전환됩니다.

  • TaskContinuationOptions에 설정된 조건이 충족되지 않아서 연속 작업이 실행되지 않은 경우. 예를 들어 선행 작업이 Faulted 상태가 되는 경우 NotOnFaulted 옵션으로 만들어진 연속 작업은 Canceled 상태로 전환되고 실행되지 않습니다.

선행 작업이 취소되는 경우 연속 작업이 실행되지 않도록 하려면 연속 작업을 만들 때 NotOnCanceled 옵션을 지정합니다.

선행 작업과 연속 작업이 동일한 논리 작업의 두 부분을 나타내는 경우 다음 예제와 같이 두 작업 모두에 동일한 취소 토큰을 전달할 수 있습니다.

Dim someCondition As Boolean = True
Dim cts As New CancellationTokenSource
Dim task1 = New Task(Sub()
                         Dim ct As CancellationToken = cts.Token
                         While someCondition = True
                             ct.ThrowIfCancellationRequested()
                             ' Do the work here...
                             ' ...
                         End While
                     End Sub,
                     cts.Token
                     )

Dim task2 = task1.ContinueWith(Sub(antecedent)
                                   Dim ct As CancellationToken = cts.Token
                                   While someCondition = True
                                       ct.ThrowIfCancellationRequested()
                                       ' Do the work here
                                       ' ...
                                   End While
                               End Sub,
                               cts.Token)
task1.Start()
' ...
' Antecedent and/or continuation will
' respond to this request, depending on when it is made.
cts.Cancel()
Task task = new Task(() =>
{
    CancellationToken ct = cts.Token;
    while (someCondition)
    {
        ct.ThrowIfCancellationRequested();
        // Do the work.
        //...                        
    }
},
    cts.Token
    );

Task task2 = task.ContinueWith((antecedent) =>
{
    CancellationToken ct = cts.Token;

    while (someCondition)
    {
        ct.ThrowIfCancellationRequested();
        // Do the work.
        //...                        
    }
},
    cts.Token);

task.Start();
//...

// Antecedent and/or continuation will 
// respond to this request, depending on when it is made.
cts.Cancel();

선행 작업이 취소되지 않은 경우에도 이 토큰을 사용하여 연속 작업을 취소할 수 있습니다. 선행 작업이 취소된 경우에는 연속 작업이 시작되지 않습니다.

한 연속 작업이 Canceled 상태가 된 후 이후의 연속 작업에 미치는 영향은 이러한 연속 작업에 지정된 TaskContinuationOptions에 따라 달라질 수 있습니다.

삭제된 연속 작업은 시작되지 않습니다.

연속 작업 및 자식 작업

연속 작업은 선행 작업과 연결된 모든 자식 작업이 완료될 때까지 실행되지 않습니다. 또한 연속 작업은 분리된 자식 작업이 완료될 때까지 대기하지 않습니다. 선행 작업의 최종 상태는 연결된 자식 작업의 최종 상태에 따라 달라집니다. 분리된 자식 작업의 상태는 부모 작업에 영향을 주지 않습니다. 자세한 내용은 중첩된 작업 및 자식 작업을 참조하십시오.

연속 작업에서 throw된 예외 처리

선행-연속 관계는 부모-자식 관계와 다릅니다. 연속 작업에서 throw된 예외는 선행 작업에 전파되지 않습니다. 따라서 연속 작업에서 throw된 예외는 다음과 같이 다른 작업에서와 마찬가지 방법으로 처리할 수 있습니다.

  1. Wait, WaitAll 또는 WaitAny 메서드나 그에 상응하는 제네릭 메서드를 사용하여 연속 작업에서 대기합니다. 다음 예제와 같이 동일한 try(Visual Basic의 경우 (Try) 문에서 선행 작업과 연속 작업이 완료될 때까지 대기할 수 있습니다.
Dim task1 = Task(Of Integer).Factory.StartNew(Function()
                                                  Return 54
                                              End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
                                          Console.WriteLine("continuation {0}", antecedent.Result)
                                          Throw New InvalidOperationException()
                                      End Sub)

Try
    task1.Wait()
    continuation.Wait()
Catch ae As AggregateException
    For Each ex In ae.InnerExceptions
        Console.WriteLine(ex.Message)
    Next
End Try

Console.WriteLine("Exception handled. Let's move on.")
var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>
{
    Console.WriteLine("continuation {0}", antecedent.Result);
    throw new InvalidOperationException();
});

try
{
    t.Wait();
    c.Wait();
}

catch (AggregateException ae)
{
    foreach(var e in ae.InnerExceptions)
        Console.WriteLine(e.Message);
}
Console.WriteLine("Exception handled. Let's move on.");
  1. 두 번째 연속 작업을 사용하여 첫 번째 연속 작업의 Exception 속성을 관찰합니다. 자세한 내용은 예외 처리(작업 병렬 라이브러리)방법: 작업에서 throw된 예외 처리를 참조하십시오.

  2. 연속 작업이 자식 작업이고 AttachedToParent 옵션을 사용하여 만들어진 경우 이 연속 작업의 예외는 연결된 다른 자식 작업에서와 마찬가지로 부모에 의해 호출 스레드로 다시 전파됩니다. 자세한 내용은 중첩된 작업 및 자식 작업을 참조하십시오.

참고 항목

개념

작업 병렬 라이브러리

변경 기록

날짜

변경 내용

이유

2010년 6월

연속 작업의 비동기 동작에 대한 설명을 추가했습니다.

고객 의견