C# 기초부터 마스터까지 — 8화
async와 await로 비동기 작업 구성하기
본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.
네트워크나 파일 작업을 기다리는 동안 스레드를 막으면 화면과 다른 작업도 멈출 수 있다. async와 await는 기다림이 있는 작업을 이어서 실행할 수 있게 표현한다.
Task 반환하기
async Task<PlayerData> LoadPlayerAsync(
string url,
CancellationToken cancellationToken)
{
using HttpResponseMessage response =
await httpClient.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();
PlayerData? data = await response.Content
.ReadFromJsonAsync<PlayerData>(cancellationToken);
return data ?? throw new InvalidDataException("Player data is empty");
}
await를 만나면 작업이 끝날 때까지 현재 스레드를 계속 붙잡고 있는 대신 나머지 코드를 나중에 이어갈 수 있다. async 메서드는 호출자가 완료와 실패를 관찰할 수 있도록 Task를 반환하는 것이 기본이다.
취소 전달하기
using var source = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
PlayerData data = await LoadPlayerAsync(url, source.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Loading canceled");
}
취소는 스레드를 강제로 종료하는 기능이 아니다. 작업이 토큰을 확인하고 안전한 지점에서 중단하도록 요청하는 계약이다.
여러 작업 기다리기
Task<ItemData> itemTask = LoadItemsAsync(token);
Task<QuestData> questTask = LoadQuestsAsync(token);
await Task.WhenAll(itemTask, questTask);
ItemData items = await itemTask;
QuestData quests = await questTask;
서로 의존하지 않는 대기 작업은 먼저 시작한 뒤 함께 기다릴 수 있다. CPU 계산을 단순히 async로 표시한다고 자동으로 병렬화되지는 않는다.
피해야 할 흐름
Task의 .Result나 .Wait()로 동기 대기하면 스레드를 막거나 실행 환경에 따라 교착 상태를 만들 수 있다. 호출 경로를 가능한 끝까지 async로 이어가는 편이 좋다.
async void는 완료와 예외를 호출자가 기다릴 수 없으므로 이벤트 핸들러 외에는 피한다. 비동기 코드의 핵심은 키워드를 붙이는 것이 아니라 완료, 실패, 취소의 흐름을 모두 호출자에게 전달하는 것이다.