다음을 통해 공유


작업 기반 비동기 프로그래밍

TPL(작업 병렬 라이브러리)은 비동기 작업을 나타내는 작업개념을 기반으로 합니다. 어떤 면에서 작업은 스레드 또는 ThreadPool 작업 항목과 비슷하지만 더 높은 수준의 추상화입니다. 작업 병렬 처리 용어는 동시에 실행되는 하나 이상의 독립적인 작업을 나타냅니다. 작업은 다음 두 가지 주요 이점을 제공합니다.

  • 시스템 리소스를 보다 효율적이고 확장성 있게 사용합니다.

    백그라운드에서 작업은 스레드 수를 결정하고 조정하는 알고리즘으로 개선된 ThreadPool큐에 대기합니다. 이러한 알고리즘은 부하 분산을 제공하여 처리량을 최대화합니다. 이 프로세스는 작업을 비교적 간단하게 만들며, 많은 작업을 만들어 세분화된 병렬 처리를 가능하게 할 수 있습니다.

  • 스레드 또는 작업 항목에서 가능한 것보다 더 많은 프로그래밍 방식 컨트롤입니다.

    이러한 작업을 중심으로 빌드된 작업 및 프레임워크는 대기, 취소, 연속 작업, 강력한 예외 처리, 자세한 상태, 사용자 지정 예약 등을 지원하는 다양한 API 집합을 제공합니다.

두 가지 이유로 TPL은 .NET에서 다중 스레드, 비동기 및 병렬 코드를 작성하는 데 선호되는 API입니다.

암시적으로 작업 만들기 및 실행

Parallel.Invoke 메서드는 임의 문 수를 동시에 실행하는 편리한 방법을 제공합니다. 각 작업 항목에 대해 Action 대리자를 전달하기만 하면됩니다. 이러한 대리자를 만드는 가장 쉬운 방법은 람다 식을 사용하는 것입니다. 람다 식은 명명된 메서드를 호출하거나 코드를 인라인으로 제공할 수 있습니다. 다음 예제에서는 동시에 실행되는 두 개의 작업을 만들고 시작하는 기본 Invoke 호출을 보여 줍니다. 첫 번째 작업은 DoSomeWork메서드를 호출하는 람다 식으로 표현되고 두 번째 작업은 DoSomeOtherWork메서드를 호출하는 람다 식으로 표시됩니다.

메모

이 설명서는 람다 식을 사용하여 TPL에서 대리자를 정의합니다. C# 또는 Visual Basic의 람다 식에 익숙하지 않은 경우 PLINQ 및 TPL 람다 식을 참조하세요.

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

메모

Invoke 백그라운드에서 만든 Task 인스턴스 수가 제공된 대리자 수와 반드시 같은 것은 아닙니다. TPL은 특히 많은 수의 대리자를 사용하여 다양한 최적화를 사용할 수 있습니다.

자세한 내용은 방법: Parallel.Invoke를 사용하여 병렬 작업 실행참조하세요.

태스크 실행을 더 잘 제어하거나 태스크에서 값을 반환하려면 Task 개체를 더 명시적으로 사용해야 합니다.

명시적으로 작업 만들기 및 실행

값을 반환하지 않는 작업은 System.Threading.Tasks.Task 클래스로 표시됩니다. 값을 반환하는 작업은 Task상속되는 System.Threading.Tasks.Task<TResult> 클래스로 표시됩니다. 작업 개체는 인프라 세부 정보를 처리하고 작업 수명 동안 호출 스레드에서 액세스할 수 있는 메서드 및 속성을 제공합니다. 예를 들어 언제든지 작업의 Status 속성에 액세스하여 실행이 시작되었는지, 완료될 때까지 실행되었는지, 취소되었는지 또는 예외를 throw했는지 확인할 수 있습니다. 상태는 TaskStatus 열거형으로 표시됩니다.

작업을 만들 때 태스크가 실행할 코드를 캡슐화하는 사용자 대리자를 제공합니다. 대리자는 명명된 대리자, 익명 메서드 또는 람다 식으로 표현할 수 있습니다. 람다 식은 다음 예제와 같이 명명된 메서드에 대한 호출을 포함할 수 있습니다. 이 예제에는 콘솔 모드 애플리케이션이 종료되기 전에 태스크가 실행을 완료하도록 하는 Task.Wait 메서드 호출이 포함되어 있습니다.

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

public class Lambda
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Create a task and supply a user delegate by using a lambda expression.
      Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
      // Start the task.
      taskA.Start();

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                        Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Lambda
    Module Example
        Public Sub Main()
            Thread.CurrentThread.Name = "Main"

            ' Create a task and supply a user delegate by using a lambda expression. 
            Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
            ' Start the task.
            taskA.Start()

            ' Output a message from the calling thread.
            Console.WriteLine("Hello from thread '{0}'.",
                            Thread.CurrentThread.Name)
            taskA.Wait()
        End Sub
    End Module
    ' The example displays output like the following:
    '    Hello from thread 'Main'.
    '    Hello from taskA.
End Namespace

Task.Run 메서드를 사용하여 작업을 만들고 하나의 작업으로 시작할 수도 있습니다. 작업을 관리하기 위해 Run 메서드는 현재 스레드와 연결된 작업 스케줄러에 관계없이 기본 작업 스케줄러를 사용합니다. Run 메서드는 작업의 생성 및 예약을 더 자세히 제어할 필요가 없는 경우 작업을 만들고 시작하는 기본 방법입니다.

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

namespace Run;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Run
    Module Example
        Public Sub Main()
            Thread.CurrentThread.Name = "Main"

            Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

            ' Output a message from the calling thread.
            Console.WriteLine("Hello from thread '{0}'.",
                            Thread.CurrentThread.Name)
            taskA.Wait()
        End Sub
    End Module
    ' The example displays output like the following:
    '    Hello from thread 'Main'.
    '    Hello from taskA.
End Namespace

TaskFactory.StartNew 메서드를 사용하여 하나의 작업에서 작업을 만들고 시작할 수도 있습니다. 다음 예제와 같이 다음 경우에 이 메서드를 사용할 수 있습니다.

  • 만들기 및 일정은 구분할 필요가 없으며 추가 작업 만들기 옵션 또는 특정 스케줄러를 사용해야 합니다.

  • Task.AsyncState 속성을 통해 검색할 수 있는 작업에 추가 상태를 전달해야 합니다.

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

namespace TaskIntro;

class CustomData
{
    public long CreationTime;
    public int Name;
    public int ThreadNum;
}

public class AsyncState
{
    public static void Main()
    {
        Task[] taskArray = new Task[10];
        for (int i = 0; i < taskArray.Length; i++)
        {
            taskArray[i] = Task.Factory.StartNew((Object obj) =>
            {
                CustomData data = obj as CustomData;
                if (data == null) return;

                data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
            },
            new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
        }
        Task.WaitAll(taskArray);
        foreach (var task in taskArray)
        {
            var data = task.AsyncState as CustomData;
            if (data != null)
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum);
        }
    }
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)

            For Each task In taskArray
                Dim data = TryCast(task.AsyncState, CustomData)
                If data IsNot Nothing Then
                    Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                    data.Name, data.CreationTime, data.ThreadNum)
                End If
            Next
        End Sub
    End Module
    ' The example displays output like the following:
    '     Task #0 created at 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

TaskTask<TResult> 각각 TaskFactory기본 인스턴스를 반환하는 정적 Factory 속성을 노출하므로 메서드를 Task.Factory.StartNew()호출할 수 있습니다. 또한 다음 예제에서는 태스크가 System.Threading.Tasks.Task<TResult>형식이므로 각각 계산 결과를 포함하는 공용 Task<TResult>.Result 속성이 있습니다. 작업은 비동기적으로 실행되며 순서에 따라 완료될 수 있습니다. 계산이 완료되기 전에 Result 속성에 액세스하는 경우 이 속성은 값을 사용할 수 있게 될 때까지 호출 스레드를 차단합니다.

using System;
using System.Threading.Tasks;

public class Result
{
   public static void Main()
   {
        Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

        var results = new Double[taskArray.Length];
        Double sum = 0;

        for (int i = 0; i < taskArray.Length; i++) {
            results[i] = taskArray[i].Result;
            Console.Write("{0:N1} {1}", results[i],
                              i == taskArray.Length - 1 ? "= " : "+ ");
            sum += results[i];
        }
        Console.WriteLine("{0:N1}", sum);
   }

   private static Double DoComputation(Double start)
   {
      Double sum = 0;
      for (var value = start; value <= start + 10; value += .1)
         sum += value;

      return sum;
   }
}
// The example displays the following output:
//        606.0 + 10,605.0 + 100,495.0 = 111,706.0

Namespace Result
    Module Example
        Public Sub Main()
            Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
                Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
                Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

            Dim results(taskArray.Length - 1) As Double
            Dim sum As Double

            For i As Integer = 0 To taskArray.Length - 1
                results(i) = taskArray(i).Result
                Console.Write("{0:N1} {1}", results(i),
                    If(i = taskArray.Length - 1, "= ", "+ "))
                sum += results(i)
            Next
            Console.WriteLine("{0:N1}", sum)
        End Sub

        Private Function DoComputation(start As Double) As Double
            Dim sum As Double
            For value As Double = start To start + 10 Step .1
                sum += value
            Next
            Return sum
        End Function
    End Module
    ' The example displays the following output:
    '       606.0 + 10,605.0 + 100,495.0 = 111,706.0
End Namespace

자세한 내용은 방법: 작업값 반환을 참조하세요.

람다 식을 사용하여 대리자를 만드는 경우 소스 코드의 해당 시점에 표시되는 모든 변수에 액세스할 수 있습니다. 그러나 경우에 따라 특히 루프 내에서 람다는 변수를 예상대로 캡처하지 않습니다. 각 반복 후에 변경되므로 값이 아닌 변수의 참조만 캡처합니다. 다음 예제에서는 문제를 보여 줍니다. 루프 카운터를 CustomData 객체를 인스턴스화하고 그 객체의 식별자로 사용하기 위해 람다 표현식에 전달합니다. 예제의 출력과 같이 각 CustomData 개체에는 동일한 식별자가 있습니다.

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

namespace Example.Iterations;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class IterationTwo
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in the loop
      // counter. This produces an unexpected result.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj) => {
                                                 var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading

Namespace IterationsTwo
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            ' Create the task object by using an Action(Of Object) to pass in the loop
            ' counter. This produces an unexpected result.
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks}
                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    i)
            Next
            Task.WaitAll(taskArray)
        End Sub
    End Module
    ' The example displays output like the following:
    '       Task #10 created at 635116418427727841 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427727841 on thread #3.
    '       Task #10 created at 635116418427747843 on thread #3.
    '       Task #10 created at 635116418427747843 on thread #3.
    '       Task #10 created at 635116418427737842 on thread #4.
End Namespace

해당 생성자를 통해 작업에 상태 개체를 제공하여 각 반복의 값에 액세스할 수 있습니다. 다음 예제에서는 CustomData 개체를 만들 때 루프 카운터를 사용하여 이전 예제를 수정합니다. 이 개체는 람다 식에 전달됩니다. 예제의 출력에서 알 수 있듯이, 이제 각 CustomData 개체에는 개체가 인스턴스화될 때 루프 카운터의 값을 기반으로 하는 고유 식별자가 있습니다.

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

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class IterationOne
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data
      // to the Task constructor. This is useful when you need to capture outer variables
      // from within a loop.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                  Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading

Namespace IterationsOne
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            ' Create the task object by using an Action(Of Object) to pass in custom data
            ' to the Task constructor. This is useful when you need to capture outer variables
            ' from within a loop. 
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)
        End Sub
    End Module
    ' The example displays output like the following:
    '       Task #0 created at 635116412924597583 on thread #3.
    '       Task #1 created at 635116412924607584 on thread #4.
    '       Task #3 created at 635116412924607584 on thread #4.
    '       Task #4 created at 635116412924607584 on thread #4.
    '       Task #2 created at 635116412924607584 on thread #3.
    '       Task #6 created at 635116412924607584 on thread #3.
    '       Task #5 created at 635116412924607584 on thread #4.
    '       Task #8 created at 635116412924607584 on thread #4.
    '       Task #7 created at 635116412924607584 on thread #3.
    '       Task #9 created at 635116412924607584 on thread #4.
End Namespace

이 상태는 작업 대리자로 인수로 전달되며 Task.AsyncState 속성을 사용하여 작업 개체에서 액세스할 수 있습니다. 다음 예제는 이전 예제의 변형입니다. AsyncState 속성을 사용하여 람다 식에 전달된 CustomData 개체에 대한 정보를 표시합니다.

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

namespace TaskIntro;

class CustomData
{
    public long CreationTime;
    public int Name;
    public int ThreadNum;
}

public class AsyncState
{
    public static void Main()
    {
        Task[] taskArray = new Task[10];
        for (int i = 0; i < taskArray.Length; i++)
        {
            taskArray[i] = Task.Factory.StartNew((Object obj) =>
            {
                CustomData data = obj as CustomData;
                if (data == null) return;

                data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
            },
            new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
        }
        Task.WaitAll(taskArray);
        foreach (var task in taskArray)
        {
            var data = task.AsyncState as CustomData;
            if (data != null)
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum);
        }
    }
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)

            For Each task In taskArray
                Dim data = TryCast(task.AsyncState, CustomData)
                If data IsNot Nothing Then
                    Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                    data.Name, data.CreationTime, data.ThreadNum)
                End If
            Next
        End Sub
    End Module
    ' The example displays output like the following:
    '     Task #0 created at 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

작업 ID

모든 작업은 애플리케이션 도메인에서 고유하게 식별하고 Task.Id 속성을 사용하여 액세스할 수 있는 정수 ID를 받습니다. 이 ID는 Visual Studio 디버거 병렬 스택작업 창에서 작업 정보를 보는 데 유용합니다. ID는 지연적으로 생성되므로 요청될 때까지 생성되지 않습니다. 따라서 프로그램이 실행될 때마다 태스크에 다른 ID가 있을 수 있습니다. 디버거에서 작업 ID를 보는 방법에 대한 자세한 내용은 작업 창 사용 및 병렬 스택 창 사용참조하세요.

작업 만들기 옵션

작업을 만드는 대부분의 API는 TaskCreationOptions 매개 변수를 허용하는 오버로드를 제공합니다. 이러한 옵션 중 하나 이상을 지정하면 작업 스케줄러에 스레드 풀에서 작업을 예약하는 방법을 알 수 있습니다. 비트 OR 연산을 사용하여 옵션을 결합할 수 있습니다.

다음 예제에서는 LongRunningPreferFairness 옵션이 있는 작업을 보여줍니다.

var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),
                        TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

작업, 스레드 및 문화

각 스레드에는 Thread.CurrentCultureThread.CurrentUICulture 속성에 의해 각각 정의된 연결된 문화권 및 UI 문화권이 있습니다. 스레드의 문화권은 서식 지정, 구문 분석, 정렬 및 문자열 비교 작업과 같은 작업에 사용됩니다. 스레드의 UI 문화 설정은 리소스 조회에 사용됩니다.

시스템 문화권은 스레드의 기본 문화권 및 UI 문화권을 정의합니다. 그러나 CultureInfo.DefaultThreadCurrentCultureCultureInfo.DefaultThreadCurrentUICulture 속성을 사용하여 애플리케이션 도메인의 모든 스레드에 대한 기본 문화권을 지정할 수 있습니다. 스레드의 문화권을 명시적으로 설정하고 새 스레드를 시작하는 경우 새 스레드는 호출 스레드의 문화권을 상속하지 않습니다. 대신 해당 문화권이 기본 시스템 문화권입니다. 그러나 작업 기반 프로그래밍에서 태스크는 다른 스레드에서 비동기적으로 실행되는 경우에도 호출 스레드의 문화권을 사용합니다.

다음 예제에서는 간단한 그림을 제공합니다. 앱의 현재 문화권을 프랑스어(프랑스)로 변경합니다. 프랑스어(프랑스)가 이미 현재 문화권인 경우 영어(미국)로 변경됩니다. 그런 다음 새 문화권에서 통화 값으로 형식이 지정된 일부 숫자를 반환하는 formatDelegate 대리자를 호출합니다. 태스크가 대리자를 동기적으로 호출하든 비동기적으로 호출하든 상관없이, 태스크는 호출 스레드의 문화 설정을 사용합니다.

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

public class Example
{
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));

                                             output += Environment.NewLine;
                                             return output;
                                           };

       Console.WriteLine("The example is running on thread {0}",
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}",
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);

       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());

       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:");
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);

       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate);
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As Decimal = {163025412.32D, 18905365.59D}
        Dim formatString As String = "C2"
        Dim formatDelegate As Func(Of String) = Function()
                                                    Dim output As String = String.Format("Formatting using the {0} culture on thread {1}.",
                                                                                         CultureInfo.CurrentCulture.Name,
                                                                                         Thread.CurrentThread.ManagedThreadId)
                                                    output += Environment.NewLine
                                                    For Each value In values
                                                        output += String.Format("{0}   ", value.ToString(formatString))
                                                    Next
                                                    output += Environment.NewLine
                                                    Return output
                                                End Function

        Console.WriteLine("The example is running on thread {0}",
                          Thread.CurrentThread.ManagedThreadId)
        ' Make the current culture different from the system culture.
        Console.WriteLine("The current culture is {0}",
                          CultureInfo.CurrentCulture.Name)
        If CultureInfo.CurrentCulture.Name = "fr-FR" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' Execute the delegate synchronously.
        Console.WriteLine("Executing the delegate synchronously:")
        Console.WriteLine(formatDelegate())

        ' Call an async delegate to format the values using one format string.
        Console.WriteLine("Executing a task asynchronously:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

        Console.WriteLine("Executing a task synchronously:")
        Dim t2 = New Task(Of String)(formatDelegate)
        t2.RunSynchronously()
        Console.WriteLine(t2.Result)
    End Sub
End Module

' The example displays the following output:
'
'          The example is running on thread 1
'          The current culture is en-US
'          Changed the current culture to fr-FR.
'
'          Executing the delegate synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

메모

.NET Framework 4.6 이전 버전의 .NET Framework에서 작업의 문화권은 호출 스레드의 문화권이 아니라 실행되는 스레드의 문화권에 따라 결정됩니다. 비동기 작업의 경우 태스크에서 사용하는 문화권이 호출 스레드의 문화권과 다를 수 있습니다.

비동기 작업 및 문화권에 대한 자세한 내용은 CultureInfo 문서의 "문화권 및 비동기 작업 기반 작업" 섹션을 참조하세요.

작업 연속 만들기

Task.ContinueWithTask<TResult>.ContinueWith 메서드를 사용하면 선행 작업 완료될 때 시작할 작업을 지정할 수 있습니다. 연속 작업의 대리자는 선행 작업의 상태를 검사할 수 있도록 선행 작업에 대한 참조를 전달합니다. 또한 Task<TResult>.Result 속성의 값을 검색하여 선행 작업의 출력을 연속 작업에 대한 입력으로 사용할 수 있습니다.

다음 예제에서 getData 작업은 TaskFactory.StartNew<TResult>(Func<TResult>) 메서드를 호출하여 시작됩니다. processData 작업은 getData 완료되면 자동으로 시작되고 processData 완료되면 displayData 시작됩니다. getData getData 작업의 Task<TResult>.Result 속성을 통해 processData 작업에 액세스할 수 있는 정수 배열을 생성합니다. processData 태스크는 해당 배열을 처리하고 해당 형식이 Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) 메서드에 전달된 람다 식의 반환 형식에서 유추되는 결과를 반환합니다. displayData 작업은 processData 완료되면 자동으로 실행되고 processData 람다 식에서 반환된 Tuple<T1,T2,T3> 개체는 processData 작업의 Task<TResult>.Result 속성을 통해 displayData 작업에 액세스할 수 있습니다. displayData 작업은 processData 작업의 결과를 가져옵니다. 형식이 비슷한 방식으로 유추되고 Result 속성의 프로그램에서 사용할 수 있게 되는 결과를 생성합니다.

using System;
using System.Threading.Tasks;

public class ContinuationOne
{
   public static void Main()
   {
      var getData = Task.Factory.StartNew(() => {
                                             Random rnd = new Random();
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;

                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } );
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2,
                                                                         x.Result.Item3);
                                                 } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Namespace ContinuationsOne
    Module Example
        Public Sub Main()
            Dim getData = Task.Factory.StartNew(Function()
                                                    Dim rnd As New Random()
                                                    Dim values(99) As Integer
                                                    For ctr = 0 To values.GetUpperBound(0)
                                                        values(ctr) = rnd.Next()
                                                    Next
                                                    Return values
                                                End Function)
            Dim processData = getData.ContinueWith(Function(x)
                                                       Dim n As Integer = x.Result.Length
                                                       Dim sum As Long
                                                       Dim mean As Double

                                                       For ctr = 0 To x.Result.GetUpperBound(0)
                                                           sum += x.Result(ctr)
                                                       Next
                                                       mean = sum / n
                                                       Return Tuple.Create(n, sum, mean)
                                                   End Function)
            Dim displayData = processData.ContinueWith(Function(x)
                                                           Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                                   x.Result.Item1, x.Result.Item2,
                                                                                   x.Result.Item3)
                                                       End Function)
            Console.WriteLine(displayData.Result)
        End Sub
    End Module
    ' The example displays output like the following:
    '   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace

Task.ContinueWith 인스턴스 메서드이므로 각 선행 작업에 대한 Task<TResult> 개체를 인스턴스화하는 대신 메서드 호출을 함께 연결할 수 있습니다. 다음 예제는 Task.ContinueWith 메서드에 대한 호출을 함께 연결한다는 점을 제외하고 이전 예제와 기능적으로 동일합니다. 메서드 호출 체인에서 반환된 Task<TResult> 개체는 마지막 연속 작업입니다.

using System;
using System.Threading.Tasks;

public class ContinuationTwo
{
   public static void Main()
   {
      var displayData = Task.Factory.StartNew(() => {
                                                 Random rnd = new Random();
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;

                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ).
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2,
                                                             x.Result.Item3);
                                     } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Namespace ContinuationsTwo
    Module Example
        Public Sub Main()
            Dim displayData = Task.Factory.StartNew(Function()
                                                        Dim rnd As New Random()
                                                        Dim values(99) As Integer
                                                        For ctr = 0 To values.GetUpperBound(0)
                                                            values(ctr) = rnd.Next()
                                                        Next
                                                        Return values
                                                    End Function). _
            ContinueWith(Function(x)
                             Dim n As Integer = x.Result.Length
                             Dim sum As Long
                             Dim mean As Double

                             For ctr = 0 To x.Result.GetUpperBound(0)
                                 sum += x.Result(ctr)
                             Next
                             mean = sum / n
                             Return Tuple.Create(n, sum, mean)
                         End Function). _
            ContinueWith(Function(x)
                             Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                 x.Result.Item1, x.Result.Item2,
                                                 x.Result.Item3)
                         End Function)
            Console.WriteLine(displayData.Result)
        End Sub
    End Module
    ' The example displays output like the following:
    '   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace

ContinueWhenAllContinueWhenAny 메서드를 사용하면 여러 작업에서 계속할 수 있습니다.

자세한 내용은 연속 작업을 사용하여 작업을 연결하기를 참조하세요.

분리된 자식 작업 생성하기

작업에서 실행 중인 사용자 코드가 새 작업을 만들고 AttachedToParent 옵션을 지정하지 않는 경우 새 작업은 특별한 방식으로 부모 작업과 동기화되지 않습니다. 이러한 비동기 작업은 분리된 중첩 작업 또는 분리된 자식 작업이라고 합니다. 다음 예제에서는 하나의 독립적인 자식 작업을 생성하는 작업을 보여줍니다.

var outer = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Outer task beginning.");

    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000000);
        Console.WriteLine("Detached task completed.");
    });
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
//    Outer task beginning.
//    Outer task completed.
//    Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Outer task beginning.")
                                      Dim child = Task.Factory.StartNew(Sub()
                                                                            Thread.SpinWait(5000000)
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
'     Outer task beginning.
'     Outer task completed.
'    Detached child completed.

메모

부모 작업은 분리된 자식 작업이 완료되기를 기다리지 않습니다.

자식 작업 만들기

작업에서 실행 중인 사용자 코드가 AttachedToParent 옵션을 사용하여 작업을 만들면 새 작업을 부모 태스크의 연결된 자식 태스크이라고 합니다. 부모 작업은 연결된 모든 자식 작업이 완료될 때까지 암시적으로 대기하므로 AttachedToParent 옵션을 사용하여 구조적 작업 병렬 처리를 표현할 수 있습니다. 다음 예제에서는 10 개 연결된 자식 작업을 만드는 부모 작업을 보여 줍니다. 이 예제에서는 Task.Wait 메서드를 호출하여 부모 작업이 완료되기를 기다립니다. 연결된 자식 작업이 완료되기를 명시적으로 기다릴 필요는 없습니다.

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

public class Child
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
                      Console.WriteLine("Parent task beginning.");
                      for (int ctr = 0; ctr < 10; ctr++) {
                         int taskNo = ctr;
                         Task.Factory.StartNew((x) => {
                                                  Thread.SpinWait(5000000);
                                                  Console.WriteLine("Attached child #{0} completed.",
                                                                    x);
                                               },
                                               taskNo, TaskCreationOptions.AttachedToParent);
                      }
                   });

      parent.Wait();
      Console.WriteLine("Parent task completed.");
   }
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.
Imports System.Threading

Namespace Child
    Module Example
        Public Sub Main()
            Dim parent = Task.Factory.StartNew(Sub()
                                                   Console.WriteLine("Parent task beginning.")
                                                   For ctr As Integer = 0 To 9
                                                       Dim taskNo As Integer = ctr
                                                       Task.Factory.StartNew(Sub(x)
                                                                                 Thread.SpinWait(5000000)
                                                                                 Console.WriteLine("Attached child #{0} completed.",
                                                                                                 x)
                                                                             End Sub,
                                                       taskNo, TaskCreationOptions.AttachedToParent)
                                                   Next
                                               End Sub)
            parent.Wait()
            Console.WriteLine("Parent task completed.")
        End Sub
    End Module
    ' The example displays output like the following:
    '       Parent task beginning.
    '       Attached child #9 completed.
    '       Attached child #0 completed.
    '       Attached child #8 completed.
    '       Attached child #1 completed.
    '       Attached child #7 completed.
    '       Attached child #2 completed.
    '       Attached child #6 completed.
    '       Attached child #3 completed.
    '       Attached child #5 completed.
    '       Attached child #4 completed.
    '       Parent task completed.
End Namespace

부모 작업은 TaskCreationOptions.DenyChildAttach 옵션을 사용하여 다른 작업이 부모 작업에 연결되지 않도록 할 수 있습니다. 자세한 내용은 연결된 자식 작업 및 분리된 자식 작업을 참조하세요.

작업이 완료 될 때까지 대기

System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult> 형식은 작업이 완료되기를 기다릴 수 있는 Task.Wait 메서드의 여러 오버로드를 제공합니다. 또한 정적 Task.WaitAllTask.WaitAny 메서드의 오버로드를 사용하면 태스크 배열의 모든 또는 전부가 완료되기를 기다릴 수 있습니다.

일반적으로 다음과 같은 이유 중 하나로 작업을 기다립니다.

  • 주 스레드는 태스크에서 계산한 최종 결과에 따라 달라집니다.

  • 작업에서 발생할 수 있는 예외를 처리해야 합니다.

  • 모든 태스크가 실행을 완료하기 전에 애플리케이션이 종료될 수 있습니다. 예를 들어 Main 모든 동기 코드(애플리케이션 진입점)가 실행된 후 콘솔 애플리케이션이 종료됩니다.

다음 예제에서는 예외 처리를 포함하지 않는 기본 패턴을 보여 줍니다.

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

// Continue on this thread...
Dim tasks() =
{
    Task.Factory.StartNew(Sub() MethodA()),
    Task.Factory.StartNew(Sub() MethodB()),
    Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.
Task.WaitAll(tasks)

' Continue on this thread...

예외 처리를 보여 주는 예제는 예외 처리참조하세요.

일부 오버로드는 시간 초과를 지정할 수 있게 해주며, 다른 오버로드는 대기 시간을 프로그래밍 방식으로 취소하거나 사용자 입력에 반응하여 취소할 수 있도록 추가 입력 매개 변수 CancellationToken를 받을 수 있습니다.

작업을 기다릴 때, TaskCreationOptions.AttachedToParent 옵션을 사용하여 생성된 해당 작업의 모든 하위 작업을 암시적으로 기다립니다. Task.Wait 작업이 이미 완료된 경우 즉시 반환됩니다. Task.Wait 메서드는 태스크가 완료된 후 Task.Wait 메서드가 호출된 경우에도 태스크에서 발생한 예외를 throw합니다.

과제 작성

TaskTask<TResult> 클래스는 여러 작업을 작성하는 데 도움이 되는 몇 가지 방법을 제공합니다. 이러한 메서드는 일반적인 패턴을 구현하고 C#, Visual Basic 및 F#에서 제공하는 비동기 언어 기능을 더 잘 사용합니다. 이 섹션에서는 WhenAll, WhenAny, DelayFromResult 메서드에 대해 설명합니다.

Task.WhenAll

Task.WhenAll 메서드는 여러 Task 또는 Task<TResult> 개체가 완료되기를 비동기적으로 기다립니다. 균일하지 않은 작업 집합을 기다릴 수 있는 오버로드된 버전을 제공합니다. 예를 들어 한 메서드 호출에서 여러 TaskTask<TResult> 개체가 완료되기를 기다릴 수 있습니다.

Task.WhenAny

Task.WhenAny 메서드는 여러 Task 또는 Task<TResult> 개체 중 하나가 완료되기를 비동기적으로 기다립니다. Task.WhenAll 메서드에서와 같이 이 메서드는 균일하지 않은 작업 집합을 기다릴 수 있도록 오버로드된 버전을 제공합니다. WhenAny 메서드는 다음 시나리오에서 특히 유용합니다.

  • 중복 작업: 여러 가지 방법으로 수행할 수 있는 알고리즘 또는 작업을 고려합니다. WhenAny 메서드를 사용하여 먼저 완료되는 작업을 선택한 다음 나머지 작업을 취소할 수 있습니다.

  • 인터리브 작업 : 완료되어야 하는 여러 작업을 시작하고, 각 작업이 완료될 때 결과를 처리하기 위해 WhenAny 메서드를 사용할 수 있습니다. 한 작업이 완료되면 하나 이상의 작업을 시작할 수 있습니다.

  • 제한된 작업: WhenAny 메서드를 사용하여 동시 작업 수를 제한하여 이전 시나리오를 확장할 수 있습니다.

  • 만료된 작업: WhenAny 메서드를 사용하여 하나 이상의 작업과 특정 시간 후에 완료되는 작업(예: Delay 메서드에서 반환되는 작업) 중에서 선택할 수 있습니다. Delay 메서드는 다음 섹션에서 설명합니다.

작업 지연

Task.Delay 메서드는 지정된 시간 후에 완료되는 Task 개체를 생성합니다. 이 메서드를 사용하여 데이터를 폴링하는 루프를 빌드하고, 시간 초과를 지정하고, 사용자 입력 처리를 지연시키는 등의 작업을 수행할 수 있습니다.

Task(T).FromResult

Task.FromResult 메서드를 사용하여 미리 계산된 결과를 포함하는 Task<TResult> 개체를 만들 수 있습니다. 이 메서드는 Task<TResult> 개체를 반환하는 비동기 작업을 수행하고 해당 Task<TResult> 개체의 결과가 이미 계산된 경우에 유용합니다. FromResult 사용하여 캐시에 저장된 비동기 다운로드 작업의 결과를 검색하는 예제는 방법: 미리 계산된 작업 만들기참조하세요.

태스크에서 예외 처리

태스크에서 하나 이상의 예외를 throw하면 예외가 AggregateException 예외로 감싸집니다. 이 예외는 태스크와 조인되는 스레드로 다시 전파됩니다. 일반적으로 작업이 완료되기를 기다리는 스레드이거나 Result 속성에 액세스하는 스레드입니다. 이 동작은 기본적으로 처리되지 않은 모든 예외가 프로세스를 종료해야 하는 .NET Framework 정책을 적용합니다. 호출 코드는 try/catch 블록에서 다음 중 어느 것을 사용하여 예외를 처리할 수 있습니다.

조인 스레드는 작업이 가비지 수집되기 전에 Exception 속성에 액세스하여 예외를 처리할 수도 있습니다. 이 속성에 액세스하면 처리되지 않은 예외가 개체가 종료될 때 프로세스를 종료하는 예외 전파 동작을 트리거하지 않습니다.

예외 및 작업에 대한 자세한 내용은 예외 처리참조하세요.

작업 취소

Task 클래스는 협조적 취소를 지원하며 .NET Framework 4에서 도입된 System.Threading.CancellationTokenSourceSystem.Threading.CancellationToken 클래스와 완전히 통합됩니다. System.Threading.Tasks.Task 클래스의 많은 생성자는 CancellationToken 개체를 입력 매개 변수로 사용합니다. 대부분의 StartNewRun 오버로드에는 CancellationToken 매개 변수도 포함됩니다.

CancellationTokenSource 클래스를 사용하여 토큰을 만들고 나중에 취소 요청을 실행할 수 있습니다. 토큰을 Task에 인수로 전달하고, 취소 요청에 응답하는 일을 하는 유저 대리자에서 동일한 토큰을 참조합니다.

자세한 내용은 작업 취소방법: 작업 취소 및 해당 자식참조하세요.

TaskFactory 클래스

TaskFactory 클래스는 작업 및 연속 작업을 만들고 시작하기 위한 일반적인 패턴을 캡슐화하는 정적 메서드를 제공합니다.

기본 TaskFactoryTask 클래스 또는 Task<TResult> 클래스에서 정적 속성으로 액세스할 수 있습니다. TaskFactory 직접 인스턴스화하고 CancellationToken, TaskCreationOptions 옵션, TaskContinuationOptions 옵션 또는 TaskScheduler포함하는 다양한 옵션을 지정할 수도 있습니다. 작업 팩터리를 만들 때 지정된 옵션은 해당 팩터리가 생성하는 모든 작업에 적용됩니다. 그러나 TaskCreationOptions 열거형을 사용하여 Task를 생성하는 경우, 태스크의 옵션이 작업 팩터리의 옵션을 재정의하게 됩니다.

대리자가 없는 작업

경우에 따라 Task 사용하여 사용자 대리자 대신 외부 구성 요소에서 수행하는 일부 비동기 작업을 캡슐화할 수 있습니다. 작업이 비동기 프로그래밍 모델 시작/끝 패턴을 기반으로 하는 경우 FromAsync 메서드를 사용할 수 있습니다. 그렇지 않은 경우에는 TaskCompletionSource<TResult> 개체를 사용하여 작업을 태스크로 감싸면 Task 프로그래밍 가능성의 일부 이점을 얻을 수 있습니다. 예를 들어 예외 전파 및 연속을 지원합니다. 자세한 내용은 TaskCompletionSource<TResult>참조하세요.

사용자 지정 스케줄러

대부분의 애플리케이션이나 라이브러리 개발자는 태스크가 실행되는 프로세서, 작업을 다른 작업과 어떻게 동기화하는지, 또는 System.Threading.ThreadPool에서 어떻게 예약되는지에 대해 신경 쓰지 않습니다. 호스트 컴퓨터에서 가능한 한 효율적으로 실행하기만 하면 됩니다. 예약 세부 정보를 보다 세밀하게 제어해야 하는 경우 TPL을 사용하면 기본 작업 스케줄러에서 일부 설정을 구성할 수 있으며 사용자 지정 스케줄러를 제공할 수도 있습니다. 자세한 내용은 TaskScheduler참조하세요.

TPL에는 병렬 및 순차적 시나리오에서 유용한 몇 가지 새로운 공용 형식이 있습니다. 여기에는 System.Collections.Concurrent 네임스페이스의 스레드로부터 안전하고 빠르고 확장 가능한 여러 컬렉션 클래스와 몇 가지 새로운 동기화 유형이 포함됩니다. 예를 들어 System.Threading.SemaphoreSystem.Threading.ManualResetEventSlim, 특정 종류의 워크로드에 대한 선행 작업보다 더 효율적입니다. .NET Framework 4의 다른 새 형식(예: System.Threading.BarrierSystem.Threading.SpinLock)은 이전 릴리스에서 사용할 수 없었던 기능을 제공합니다. 자세한 내용은 병렬 프로그래밍 대한데이터 구조를 참조하세요.

사용자 지정 작업 유형

우리는 System.Threading.Tasks.Task 또는 System.Threading.Tasks.Task<TResult>을 상속하지 않는 것을 권장합니다. 대신 AsyncState 속성을 사용하여 추가 데이터 또는 상태를 Task 또는 Task<TResult> 개체와 연결하는 것이 좋습니다. 확장 메서드를 사용하여 TaskTask<TResult> 클래스의 기능을 확장할 수도 있습니다. 확장 메서드에 대한 자세한 내용은 확장 메서드확장 메서드참조하세요.

Task 또는 Task<TResult>상속해야 하는 경우 Run 또는 System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult>또는 System.Threading.Tasks.TaskCompletionSource<TResult> 클래스를 사용하여 사용자 지정 작업 유형의 인스턴스를 만들 수 없습니다. 이러한 클래스는 TaskTask<TResult> 개체만 만들기 때문에 그들을 사용할 수 없습니다. 또한 Task, Task<TResult>, TaskFactoryTaskFactory<TResult> 제공하는 작업 연속 메커니즘을 사용하여 사용자 지정 작업 유형의 인스턴스를 만들 수 없습니다. 이러한 클래스는 TaskTask<TResult> 개체만 만들기 때문에 사용할 수 없습니다.

타이틀 묘사
연속 작업을 사용하여 작업 체이닝 연속 작업의 작동 방식을 설명합니다.
연결된 자식 작업 및 분리된 자식 작업 연결된 자식 작업과 분리된 자식 작업의 차이점들을 설명합니다.
작업 취소 Task 개체에 기본 제공되는 취소 지원에 대해 설명합니다.
예외 처리 동시 스레드의 예외를 처리하는 방법을 설명합니다.
방법: Parallel.Invoke를 사용하여 병렬 작업 실행 Invoke사용하는 방법을 설명합니다.
방법: 작업에서 값을 반환하는 방법 작업에서 값을 반환하는 방법을 설명합니다.
방법: 작업 및 해당 자식 취소 작업을 취소하는 방법을 설명합니다.
방법: 미리 계산된 작업 만들기 Task.FromResult 메서드를 사용하여 캐시에 저장된 비동기 다운로드 작업의 결과를 검색하는 방법을 설명합니다.
방법: 병렬 작업을 사용하여 이진 트리 순회 태스크를 사용하여 이진 트리를 트래버스하는 방법을 설명합니다.
방법: 중첩 작업 래프 해제 Unwrap 확장 메서드를 사용하는 방법을 보여 줍니다.
데이터 병렬 처리 ForForEach 사용하여 데이터에 대한 병렬 루프를 만드는 방법을 설명합니다.
병렬 프로그래밍 .NET Framework 병렬 프로그래밍을 위한 최상위 노드입니다.

참고하십시오