await로 기다리는 비동기 메서드에서 예외가 발생하면,
그 예외는await가 있는 줄에서 터지는 것처럼 동작하고,
그 주변의try / catch로 잡을 수 있다.
즉, “비동기라서 예외 처리가 완전 다르다”가 아니라,
“예외가 await 줄에서 터진다고 생각하면 된다” 정도로 이해하면 편하다.
try / catchtry
{
DoSomething(); // 여기서 예외 나면 바로 catch로 감
}
catch (Exception ex)
{
Console.WriteLine("에러 발생: " + ex.Message);
}
DoSomething() 안에서 예외가 발생하면,
바로 위의 try / catch 블록으로 예외가 전파된다.
await + try / catchtry
{
await DoSomethingAsync(); // 이 줄에서 Task가 완료될 때 예외가 있으면 여기서 throw
}
catch (Exception ex)
{
Console.WriteLine("에러 발생: " + ex.Message);
}
비동기 메서드 안에서 예외가 나면:
await할 때, 그 줄에서 예외를 다시 던진다(throw).await를 둘러싼 try / catch에서 예외를 잡을 수 있다.
결국 “예외는 await 줄에서 터진다”고 보면 된다.
static async Task Main()
{
try
{
Console.WriteLine("요청 시작");
string result = await DownloadDataAsync(); // 여기에서 예외가 다시 throw 됨
Console.WriteLine("결과: " + result);
}
catch (Exception ex)
{
Console.WriteLine("예외 잡음: " + ex.Message);
}
Console.WriteLine("메인 계속 실행");
}
static async Task<string> DownloadDataAsync()
{
// 예를 들어 HttpClient 호출 같은 비동기 작업이라고 가정
await Task.Delay(1000); // 네트워크 대기라고 생각
// 일부러 에러 발생
throw new InvalidOperationException("다운로드 중 오류 발생!");
}
실행 흐름을 찬찬히 따라가면:
DownloadDataAsync()를 호출 → Task<string>를 반환await가 이 Task가 끝날 때까지 기다린다.DownloadDataAsync 내부에서 throw 발생.await 지점으로 돌아올 때, 그 예외를 다시 던진다.try / catch에서 이 예외를 잡는다.
그래서 “예외는 DownloadDataAsync 안에서 났지만,
실제로 잡는 위치는 await DownloadDataAsync() 줄의 catch라고 보면 된다.
static async Task DownloadAndPrintAsync()
{
try
{
string result = await DownloadDataAsync();
Console.WriteLine("결과: " + result);
}
catch (Exception ex)
{
Console.WriteLine("DownloadAndPrintAsync 내부에서 처리: " + ex.Message);
}
}
DownloadAndPrintAsync 안에서 끝까지 처리된다.static async Task DownloadAndPrintAsync()
{
// 여기서는 예외를 처리하지 않고 위로 올림
string result = await DownloadDataAsync();
Console.WriteLine("결과: " + result);
}
static async Task Main()
{
try
{
await DownloadAndPrintAsync();
}
catch (Exception ex)
{
Console.WriteLine("Main에서 예외 처리: " + ex.Message);
}
}
DownloadAndPrintAsync 안에서 예외 처리 없이 그대로 던진다.Main 또는 UI 레이어)에서 한 번에 예외를 처리하는 구조.결국 “예외를 어디 레벨에서 책임지고 처리할 것인가?”를 설계하는 문제다.
await를 안 쓰면 어떻게 되나? (중요 포인트)// ❌ 이렇게 하면 예외가 try/catch에서 안 잡힐 수 있음
try
{
Task t = DoSomethingAsync(); // Task만 만든 상태, await 안 함
// 다른 작업...
}
catch (Exception ex)
{
Console.WriteLine("여기선 예외 못 잡음");
}
await를 하지 않으면, 예외가 **Task 안에서만** 발생하고 호출 스택으로 올라오지 않는다.t.Wait()나 t.Result를 사용할 때 예외가 튀어나오는데,AggregateException 같은 형태로 감싸져서 나와서 더 복잡해진다.
그래서 “비동기 예외를 try / catch로 제대로 처리하고 싶으면 반드시 await 해야 한다”라고 생각하면 편하다.
여러 개 Task를 동시에 돌려놓고 한 번에 기다릴 때도 패턴은 똑같다.
try
{
Task t1 = DoWorkAsync(1);
Task t2 = DoWorkAsync(2);
Task t3 = DoWorkAsync(3);
await Task.WhenAll(t1, t2, t3); // 여기서 여러 Task 예외가 한 번에 표출
}
catch (Exception ex)
{
Console.WriteLine("WhenAll에서 예외 잡음: " + ex.Message);
}
Task.WhenAll에 들어간 Task 중 하나라도 예외가 나면,await Task.WhenAll(...) 줄에서 예외가 터진다.try / catch로 감싸면 예외를 잡을 수 있다.AggregateException에 담기기도 한다.WhenAll에서 한 번에 터진다” 정도만 기억해 두면 충분하다.static async Task DoSomethingSafeAsync()
{
try
{
await DoSomethingDangerousAsync();
}
catch (Exception ex)
{
// 여기서 로그 찍고 끝내기
Console.WriteLine("내부 처리: " + ex.Message);
}
}
static async Task DoSomethingAsync()
{
await DoSomethingDangerousAsync(); // 예외를 위로 던짐
}
static async Task Main()
{
try
{
await DoSomethingAsync();
}
catch (Exception ex)
{
Console.WriteLine("최상위에서 처리: " + ex.Message);
}
}
상황에 따라 “어디서 예외를 책임질지”를 선택해서 이 두 패턴을 섞어 쓰게 된다.