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 작업;`은 `실행_카운트_비동기`의 모든 로직이 끝날 때까지 기다리게 됩니다. 이렇게 하면 코드의 실행 순서가 올바르게 보장됩니다.