its_jh_stroy

[C#] Task와 async, await 본문

C#

[C#] Task와 async, await

_J_H_ 2023. 11. 15. 23:23

비동기 프로그래밍

비동기란 여러 가지 작업을 동시에 수행할 수 있도록 하는 방식이다.

C#에서 비동기 프로그래밍을 하는 방식 중 하나로 Task와 async, await 키워드를 사용하는 것이 있다.

 

Task

Task는 메서드와 같은 작업 단위를 비동기적으로 실행할 수 있도록 지원하는 .NET 클래스이다.

C#에서는 반환값 유무에 따라 Task와 Task<TResult>와 같은 형태로 사용된다.

 

async await

async와 await는 비동기 프로그래밍을 하기 위한 키워드이다.

아래는 각 키워드에 대한 설명이다.

async

- 해당 메서드가 비동기적으로 실행될 수 있음을 표시하는 역할을 한다.

- async 메서드의 반환 타입은 void, Task, Task<TResult> 타입 중 하나이다.

    * void는 await를 사용할 수 없고 예외 처리와 같은 작업을 할 수 없기 때문에 권장되지 않는다.

await

- 메서드 실행이 끝날 때까지 호출한 메서드에 제어를 반환하고 대기한다.

    * 따라서 호출한 메서드는 대기하는 동안 다른 작업을 수행할 수 있다.

- await 뒤에 오는 메서드는 반드시 Task 또는 Task<TResult>를 반환한다.

- Task<TResult>를 반환하는 비동기 작업에서 결과를 받아오는 역할도 한다.

 

동기적인 작업

식당에 손님이 입장하여 음식을 주문하는 것을 코드로 구현해 보았다.

손님이 음식을 주문하고, 음식이 나오는 동안 기다리는 상황을 의도한다.

static void Main(string[] args)
{
    Console.WriteLine($"[Main 스레드 ID: {Thread.CurrentThread.ManagedThreadId}]");
    Console.WriteLine("손님이 음식을 주문합니다.");

    Cook();

    Console.WriteLine("음식을 기다리는 중..."); // (1)
    Console.ReadLine();
}


// 여기에서는 void 타입을 반환해도 상관 없음
static async Task Cook()
{
    Console.WriteLine($"[MakeFood 스레드 ID: {Thread.CurrentThread.ManagedThreadId}]");
    Console.WriteLine("음식 준비 중..."); // (2)
    await Task.Delay(1000);
    Console.WriteLine("음식이 완료되었습니다.");
}

 

Main과 Cook 메서드 모두 동일한 스레드를 사용하는 동기적인 동작을 수행한다.

(2)를 수행하고 await를 만나 1초를 기다리는 동안 Cook 메서드를 호출한 Main 메서드로 실행 흐름이 넘어간다.

따라서 (2)가 (1)보다 먼저 출력되는 것을 확인할 수 있다.

 

비동기적인 작업

반환값이 없는 Task

이제 Task를 이용하여 이것을 비동기적인 코드로 바꾸어 볼 것이다.

Main 함수만 아래와 같이 바꾸어보자.

static void Main(string[] args)
{
    Console.WriteLine($"[Main 스레드 ID: {Thread.CurrentThread.ManagedThreadId}]");
    Console.WriteLine("손님이 음식을 주문합니다.");

    Task.Run(MakeFood);

    Console.WriteLine("음식을 기다리는 중..."); // (1)
    Console.ReadLine();
}

 

메서드를 직접 호출하지 않고 Task.Run 메서드를 통해 호출했다.

이것은 새로운 스레드를 만들어 비동기 작업을 수행하는 것이다.

Main과 Cook 메서드가 서로 다른 스레드를 사용하는 것을 확인할 수 있고, (1)이 (2)보다 먼저 출력된다.

 

반환값이 있는 Task

위 코드에서 사용된 MakeFood 메서드는 반환값이 없다.

만약 반환값이 있는 비동기 작업을 처리해야한다면 Task<TResult> 형태로 사용할 수 있다.

비동기 작업의 결과를 얻으려면 await를 사용해야 한다.따라서 메서드를 먼저 실행해두고, 결과가 필요한 시점에서 await를 사용하여 결과를 받아오는 방식으로 코드를 구성할 수 있다.

static void Main(string[] args)
{
    Start();
    Console.ReadLine();
}


static async void Start()
{
    Console.WriteLine($"[메인 스레드 ID: {Thread.CurrentThread.ManagedThreadId}]");
    Console.WriteLine("손님이 음식을 주문합니다.");

    Task<string> cookTask = Task.Run(CookAsync);
    Console.WriteLine("손님들이 음식을 기다리는 중 ...");

    // 결과가 필요한 시점에 대기
    string result = await cookTask;
    Console.WriteLine(result);
}


static async Task<string> CookAsync()
{
    Console.WriteLine($"비동기 작업 스레드 ID: {Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine("음식 준비 중...");
    await Task.Delay(2000);
    return "음식이 완료되었습니다.";
}