Task<Tresult>클래스(코드의 비동기 실행 결과를 줌)

00·2025년 1월 13일

C#

목록 보기
144/149

Task<TResult> 클래스는 C#에서 비동기 작업의 결과를 나타내는 데 사용되는 클래스이다.

마치 음식 주문과 같다. 음식을 주문하면 주문서는 접수되지만 음식이 바로 나오는 것은 아니다. 요리사가 음식을 준비하는 동안 다른 일을 할 수 있다. 음식이 준비되면 주문서에 적힌 테이블로 음식이 전달된다.

Task<TResult>는 이 주문서와 같은 역할을 한다. 비동기 작업을 시작하면 Task<TResult> 객체가 생성되고, 작업이 완료될 때까지 기다리지 않고 다른 작업을 수행할 수 있다. 작업이 완료되면 Task<TResult> 객체의 Result 프로퍼티를 통해 결과 값을 가져올 수 있다.

Task<TResult> 클래스는 .NET Framework 4에서 처음 도입되었으며, TAP(Task-based Asynchronous Pattern)의 핵심 요소 중 하나이다. TAP는 C#에서 비동기 프로그래밍을 구현하기 위한 패턴으로, asyncawait 키워드를 사용하여 비동기 코드를 동기 코드처럼 작성할 수 있도록 지원한다.

Task<TResult> 클래스의 주요 특징

  • 제네릭 타입: TResult는 작업의 결과 타입을 나타내는 제네릭 타입 매개변수이다. 예를 들어, Task<int>는 정수 값을 반환하는 작업을 나타낸다.
  • 비동기 실행: Task<TResult> 객체는 일반적으로 스레드 풀 스레드에서 비동기적으로 실행된다.
  • 결과 값: Result 프로퍼티를 통해 작업의 결과 값을 가져올 수 있다.
  • 상태 확인: Status 프로퍼티를 통해 작업의 상태(대기 중, 실행 중, 완료, 취소, 오류)를 확인할 수 있다.
  • 예외 처리: Exception 프로퍼티를 통해 작업 중 발생한 예외를 가져올 수 있다.
  • 취소: CancellationToken을 사용하여 작업을 취소할 수 있다.
  • 연속 작업: ContinueWith() 메서드를 사용하여 작업이 완료된 후 실행할 작업을 지정할 수 있다.

Task<TResult> 클래스 사용 예시

// 1초 후에 정수 42를 반환하는 Task<int> 객체 생성
Task<int> task = Task.Run(() => 
{
    Thread.Sleep(1000);
    return 42;
});

// 다른 작업 수행
Console.WriteLine("작업을 수행 중입니다...");

// Task 완료 대기
task.Wait();

// 결과 값 가져오기
int result = task.Result;

Console.WriteLine($"결과: {result}"); // 출력: 결과: 42

Task<TResult> 클래스는 C#에서 비동기 프로그래밍을 구현하는 데 유용하게 사용되는 클래스이다.


예제

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace TaskResult
{
    class MainApp
    {
        static bool IsPrime(long number)  // 주어진 수가 소수인지 판별하는 메서드이다.
        {
            if (number < 2)  // 2보다 작으면 소수가 아니다.
                return false;

            if (number % 2 == 0 && number != 2)  // 2를 제외한 짝수는 소수가 아니다.
                return false;

            for (long i = 2; i < number; i++)  // 2부터 number - 1까지의 수로 나누어 떨어지는지 확인한다.
            {
                if (number % i == 0)  // 나누어 떨어지면 소수가 아니다.
                    return false;
            }

            return true;  // 위 조건을 모두 만족하면 소수이다.
        }

        static void Main(string[] args)
        {
            long from = Convert.ToInt64(args[0]);  // 명령줄 인수의 첫 번째 값을 long 타입으로 변환하여 from에 저장한다.
            long to = Convert.ToInt64(args[1]);  // 명령줄 인수의 두 번째 값을 long 타입으로 변환하여 to에 저장한다.
            int taskCount = Convert.ToInt32(args[2]);  // 명령줄 인수의 세 번째 값을 int 타입으로 변환하여 taskCount에 저장한다.

            // 범위 내에서 소수를 찾는 Func<object, List<long>> 델리게이트를 생성한다.
            Func<object, List<long>> FindPrimeFunc =
                (objRange) =>
                {
                    long[] range = (long[])objRange;  // objRange를 long 배열로 변환한다.
                    List<long> found = new List<long>();  // 찾은 소수를 저장할 List<long> 객체를 생성한다.

                    for (long i = range[0]; i < range[1]; i++)  // range[0]부터 range[1] - 1까지의 수를 검사한다.
                    {
                        if (IsPrime(i))  // i가 소수이면 found 리스트에 추가한다.
                            found.Add(i);
                    }

                    return found;  // 찾은 소수 리스트를 반환한다.
                };

            Task<List<long>>[] tasks = new Task<List<long>>[taskCount];  // taskCount 크기의 Task<List<long>> 배열을 생성한다.
            long currentFrom = from;  // 현재 작업 범위의 시작 값을 저장할 변수를 from으로 초기화한다.
            long currentTo = to / tasks.Length;  // 현재 작업 범위의 끝 값을 저장할 변수를 to / tasks.Length로 초기화한다.
            for (int i = 0; i < tasks.Length; i++)  // tasks 배열의 각 요소에 대해 반복한다.
            {
                Console.WriteLine("Task[{0}] : {1} ~ {2}", i, currentFrom, currentTo);  // 현재 Task의 범위를 출력한다.

                tasks[i] = new Task<List<long>>(FindPrimeFunc, new long[] { currentFrom, currentTo });  // FindPrimeFunc 델리게이트를 사용하여 Task 객체를 생성하고 tasks 배열에 저장한다.
                currentFrom = currentTo + 1;  // 다음 Task의 시작 값을 설정한다.

                if (i == tasks.Length - 2)  // 마지막 Task의 경우 currentTo를 to로 설정한다.
                    currentTo = to;
                else
                    currentTo = currentTo + (to / tasks.Length);  // 그 외의 경우 currentTo를 to / tasks.Length만큼 증가시킨다.
            }

            Console.WriteLine("Please press enter to start...");  // "Please press enter to start..." 메시지를 출력한다.
            Console.ReadLine();  // 사용자 입력을 기다린다.
            Console.WriteLine("Started...");  // "Started..." 메시지를 출력한다.

            DateTime startTime = DateTime.Now;  // 현재 시간을 startTime에 저장한다.

            foreach (Task<List<long>> task in tasks)  // tasks 배열의 각 Task를 시작한다.
                task.Start();

            List<long> total = new List<long>();  // 모든 Task에서 찾은 소수를 저장할 List<long> 객체를 생성한다.

            foreach (Task<List<long>> task in tasks)  // tasks 배열의 각 Task에 대해 반복한다.
            {
                task.Wait();  // Task가 완료될 때까지 기다린다.
                total.AddRange(task.Result.ToArray());  // Task의 결과(소수 리스트)를 total 리스트에 추가한다.
            }
            DateTime endTime = DateTime.Now;  // 현재 시간을 endTime에 저장한다.

            TimeSpan ellapsed = endTime - startTime;  // startTime과 endTime의 차이를 계산하여 ellapsed에 저장한다.

            // from부터 to까지의 소수 개수와 경과 시간을 출력한다.
            Console.WriteLine("Prime number count between {0} and {1} : {2}", from, to, total.Count);
            Console.WriteLine("Ellapsed time : {0}", ellapsed);
        }
    }
}

코드 설명

이 C# 코드는 지정된 범위 내에서 소수(prime number)를 찾는 프로그램이다. 여러 개의 Task를 사용하여 작업을 병렬로 처리하고, 각 Task의 결과를 합쳐서 전체 소수 개수를 계산한다.

  • IsPrime(long number) 메서드: 입력으로 받은 number가 소수인지 판별하여 bool 값을 반환한다.
  • Main 메서드:
    • 명령줄 인수에서 소수를 찾을 범위(from, to)와 Task 개수(taskCount)를 가져온다.
    • FindPrimeFunc 델리게이트는 지정된 범위 내에서 소수를 찾는 함수를 나타낸다.
    • taskCount 개수만큼 Task<List<long>> 객체를 생성하고, 각 Task는 FindPrimeFunc 델리게이트를 사용하여 지정된 범위 내에서 소수를 찾는다.
    • 모든 Task를 시작하고, 각 Task가 완료될 때까지 기다린다.
    • 각 Task의 결과를 합쳐서 전체 소수 개수를 계산하고, 실행 시간을 측정하여 출력한다.

병렬 처리, 비동기 처리
'병렬 처리'와 '비동기 처리'는 다른 개념이지만, Task를 사용하면 둘 다 구현할 수 있다.

병렬 처리는 여러 개의 작업을 동시에 실행하는 것을 의미한다. 마치 여러 명의 요리사가 동시에 요리하는 것과 같다.

비동기 처리는 작업이 완료될 때까지 기다리지 않고 다른 작업을 먼저 수행하는 것을 의미한다. 마치 요리 주문을 받고, 요리사가 요리하는 동안 다른 손님을 응대하는 것과 같다.

Task는 비동기 작업을 나타내는 클래스이다. Task를 사용하면 작업을 스레드 풀에 등록하여 비동기적으로 실행할 수 있다.

  • 병렬 처리: 여러 개의 Task를 생성하고 Start() 메서드를 호출하면, 각 Task는 스레드 풀의 스레드에서 병렬로 실행된다.
  • 비동기 처리: Task를 생성하고 Wait() 메서드를 호출하지 않으면, Task는 백그라운드에서 비동기적으로 실행되고, 메인 스레드는 다른 작업을 계속 수행할 수 있다.

이 코드에서는 Task를 사용하여 소수를 찾는 작업을 병렬로 처리하고 있다. taskCount 개수만큼 Task 객체를 생성하고, 각 Task는 지정된 범위 내에서 소수를 찾는다.

하지만 Task는 비동기적으로 실행되기 때문에, 메인 스레드는 Task가 완료될 때까지 기다리지 않고 다른 작업을 수행할 수 있다.

엄밀히 말하면 이 코드는 병렬 처리와 비동기 처리를 모두 사용하고 있다고 볼 수 있다.

  • 병렬 처리: 여러 개의 Task를 사용하여 소수를 찾는 작업을 병렬로 실행한다.
  • 비동기 처리: Task를 비동기적으로 실행하고, 메인 스레드는 Task가 완료될 때까지 기다리지 않고 다른 작업을 수행한다.

이처럼 Task는 병렬 처리와 비동기 처리를 모두 지원하는 유연한 클래스이다.

0개의 댓글