[로봇활용_14주차] C# Task

최윤호·2025년 11월 8일
post-thumbnail

비동기 작업의 약속

지난 글에서는 async/await에 대해 배웠습니다.

await Task.Delay(3000);

여기서 await가 기다리는 '진동벨'의 정체는 정확히 무엇일까요?
Task.Delay(3000)에서 Task는 대체 무슨 역할을 하는 걸까요?
이번 글에서는 Task에 대해 깊이 파헤쳐 보겠습니다.

1)Task의 정체: 작업에 대한 약속

TaskSystem.Threading.Tasks네임스페이스에 있는 클래스입니다.
이 객체는 '미래에 완료될 비동기 작업 하나'를 나타냅니다.
단순히 '작업'이라고 생각하면 Thread와 헷갈릴 수 있습니다.
하지만 ThreadTask는 근본적으로 다릅니다.

Task는 '작업에 대한 정보'를 담은 '서약서'입니다.

'서약서'에는 다음과 같은 정보가 담겨 있습니다.

  • 이 작업이 지금 실행 중인가? (Status속성)
  • 작업이 완료되었는가? (IsCompleted속성)
  • 작업이 성공적으로 끝났는가? (IsCompletedSuccessfully속성)
  • 작업이 실패했다면, 왜 실패했는가? (IsFaulted, Exception속성)
  • 작업이 취소되었는가? (IsCanceled속성)

await키워드는 바로 이 Task라는 '서약서'을 계속 지켜보다가,
작업 상태가 '완료'로 바뀌면 다음 코드를 실행시켜주는 역할을 합니다.

2)Task, Thread의 다른 점은?

이 부분이 많은 분들이 헷갈리는 지점입니다. 비유를 통해 명확하게 구분해 봅시다.

  • Thread(스레드): 작업을 수행하는 직접 '일꾼'입니다.
    운영체제로부터 직접 할당받는 저수준의 실행 단위이죠.
  • Task(태스크): 수행해야 할 '작업 내용' 또는 '작업 지시서'입니다.
    스레드보다 한 단계 위의 고수준 추상화 개념입니다.

비유: 레스토랑 주방

  • 요리사(Thread): 실제로 요리를 하는 사람입니다.
  • 주문서(Task): '파스타 1개 만들기'라는 '작업 내용'이 적힌 종이입니다.
  • 주방장(ThreadPool): 주문서(Task)가 들어오면,
    놀고 있는 요리사(Thread)에게 주문서를 할당해 요리를 시킵니다.

Task를 사용하면 요리사(스레드)를 직접 다루는 복잡한 과정을 신경 쓸 필요가 없습니다.
그냥 '파스타 1개 만들기'라는 주문서(Task)만 주방장(ThreadPool)에게 던져주면,
주방장이 알아서 가장 효율적인 방식으로 요리사들을 관리하며 작업을 처리해 줍니다.

3)Task 사용법: Task.Run()

Task를 만드는 가장 일반적인 방법은 Task.Run()을 사용하는 것입니다.
이 메서드는 CPU 바운드 작업을 백그라운드 스레드(스레드 풀의 스레드)에서 실행시키고,
그 작업에 대한 '서약서'Task객체를 즉시 반환합니다.

[코드]

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

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Main 스레드: 무거운 작업을 시작합니다.");

        // Task.Run은 작업을 스레드 풀에 맡기고, 'Task' 객체를 바로 반환한다.
        Task heavyTask = Task.Run(() =>
        {
            // 이 람다식 안의 코드가 백그라운드 스레드에서 실행됨
            Console.WriteLine($"작업 스레드 ID: {Thread.CurrentThread.ManagedThreadId}");
            long sum = 0;
            for (long i = 0; i < 3_000_000_000; i++)
            {
                sum += i;
            }
            Console.WriteLine("작업 스레드: 무거운 작업 완료!");
        });

        Console.WriteLine($"Main 스레드: heavyTask의 상태는? -> {heavyTask.Status}");
        Console.WriteLine("Main 스레드: 다른 일을 먼저 처리합니다...");
        // ... 다른 코드 실행 ...

        // 이제 작업이 끝날 때까지 기다린다.
        await heavyTask;

        Console.WriteLine($"Main 스레드: heavyTask의 상태는? -> {heavyTask.Status}");
        Console.WriteLine("Main 스레드: 모든 작업이 완료되었습니다.");
    }
}

[실행 결과]

Main 스레드: 무거운 작업을 시작합니다.
Main 스레드: heavyTask의 상태는? -> WaitingToRun
Main 스레드: 다른 일을 먼저 처리합니다...
작업 스레드 ID: 7
작업 스레드: 무거운 작업 완료!
Main 스레드: heavyTask의 상태는? -> RanToCompletion
Main 스레드: 모든 작업이 완료되었습니다.

Task.Run이 호출되자마자 Main스레드는 멈추지 않고 다음 코드를 실행합니다.
heavyTask라는 '서약서'만 받아두고, 실제 무거운 계산은 다른 스레드에 맡긴 것이죠.
그리고 await를 만나는 시점에서 비로소 작업이 끝나기를 기다립니다.

4)정리

우리는 async/await'진동벨'이 사실은 Task라는 객체였음을 알게 되었습니다.

항목설명
Task란?비동기 작업의 상태와 진행 과정을 나타내는 '서약서'
Task vs ThreadTask는 '작업 지시서', Thread는 '일꾼', Task가 더 고수준의 추상화
Task.Run()CPU 바운드 작업을 스레드 풀에서 쉽게 실행하고 Task 객체를 얻는 방법
profile
🚀 미래의 엔지니어를 꿈꾸는 훈련생의 기록 📝

0개의 댓글