C# 비동기 프로그래밍의 함정: void 메서드와 Task 메서드의 차이

C#에서 비동기 코드를 작성할 때 흔히 겪는 문제점 중 하나는 `async void` 메서드를 `await`하는 경우입니다. 개발자는 코드 실행이 특정 지점에서 대기할 것이라 예상하지만, 실제로는 즉시 다음 코드로 넘어가는 경우가 있습니다.

아래 코드는 이 문제를 보여줍니다. `실행_카운트` 메서드는 `async void`로 선언되어 있습니다.

async static void 실행_카운트()
{
    Console.WriteLine("Task Void 시작");
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    int 숫자 = 0;
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("출력 값: {0}", 숫자);
        숫자++;
        await Task.Delay(500);
    }
    Console.WriteLine("Task Void 종료");
}

이 메서드를 다음과 같이 호출하면 문제가 발생합니다.

var 작업_void = new Task(실행_카운트);
await 작업_void;

`await HttpClient.GetAsync()`와 같은 다른 비동기 호출에서는 `await`가 예상대로 동작하여 결과를 기다립니다. 하지만 위 코드에서 `await 작업_void;`는 즉시 다음 문장으로 실행 흐름이 넘어갑니다. 이는 `실행_카운트` 메서드가 `void`를 반환하기 때문입니다. `async void` 메서드는 "실행 후 잊기(fire-and-forget)" 방식으로 동작하여, 호출 즉시 반환되기 때문입니다. 메서드 내부의 로직은 여전히 실행되지만, 호출자는 해당 작업이 언제 완료되는지 알 수 없습니다.

이 문제를 해결하려면 메서드가 `Task`를 반환하도록 수정해야 합니다. `async Task`를 반환하는 메서드는 비동기 작업의 완료를 나타내는 `Task` 객체를 제공합니다.

static void Main(string[] args)
{
    실행_엔트리_수정();
    Console.ReadLine();
}

async static Task 실행_카운트_비동기()
{
    Console.WriteLine("Task 시작");
    int 숫자 = 0;
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("출력 값: {0}", 숫자);
        숫자++;
        await Task.Delay(500);
    }
    Console.WriteLine("Task 종료");
}

async static void 실행_엔트리_수정()
{
    // async Task를 반환하는 메서드를 실행
    var 작업 = Task.Run(실행_카운트_비동기);

    // 작업이 완료될 때까지 대기
    await 작업;

    // 작업이 완료된 후 실행될 코드
    var 이어지는_작업 = 작업.ContinueWith(async (t) =>
    {
        Console.WriteLine("TaskVoid가 완료된 후 실행되는 TaskContinue, Task의 상태는 {0}", t.Status);
        await Task.Delay(1000);
    });
}

수정된 코드에서 `실행_카운트_비동기` 메서드는 `Task`를 반환합니다. 이는 호출자에게 "이것은 비동기 작업입니다"라고 알리는 역할을 합니다. `await`는 이 `Task` 객체를 통해 작업의 완료를 기다릴 수 있습니다.

중요한 점은 `Task.Run(실행_카운트_비동기)`를 사용했다는 것입니다. `Task.Run`은 내부적으로 새로운 `Task`를 생성하여 `실행_카운트_비동기` 메서드를 실행하고, 이 `Task`는 내부 작업이 완료될 때까지 완료되지 않습니다. 따라서 `await 작업;`은 `실행_카운트_비동기`의 모든 로직이 끝날 때까지 기다리게 됩니다. 이렇게 하면 코드의 실행 순서가 올바르게 보장됩니다.

태그: C# 비동기 프로그래밍 Async/Await Task

7월 8일 02:22에 게시됨