await 연산자 - 비동기 작업이 끝날 때까지 기다리기

await 연산자 - 비동기 작업이 끝날 때까지 기다리기

await 연산자는 자기 피연산자(operand)가 나타내는 비동기 작업이 완료될 때까지, 이 연산자를 감싸고 있는 async 메서드의 실행을 **중단(suspend)**시켜요. 작업이 완료되면 await 연산자는 그 작업의 결과(있다면)를 돌려줍니다. 피연산자가 이미 완료된 작업을 나타내는 경우에는 감싸고 있는 메서드를 중단하지 않고 결과를 곧바로 반환하죠. 그리고 await 연산자는 async 메서드를 실행하는 스레드를 차단하지 않아요. await가 async 메서드를 중단시키면, 제어권은 그 메서드의 호출자(caller)에게 돌아갑니다.

출처: await operator - asynchronously await for a task to complete

이건 C#에 포함된 가장 오래된 기능이라 별도의 버전 표기는 없어요. 어떤 C# 버전에서도 그대로 쓸 수 있는 기본 연산자입니다.

본문

한 예시를 보면서 흐름을 따라가 볼게요. 아래 예시에서 xref:System.Net.Http.HttpClient.GetByteArrayAsync*?displayProperty=nameWithType 메서드는 Task<byte[]> 인스턴스를 반환해요. 이 작업이 완료되면 바이트 배열을 만들어 내는 비동기 작업이죠. 작업이 끝날 때까지 await 연산자는 DownloadDocsMainPageAsync 메서드를 중단합니다. DownloadDocsMainPageAsync가 중단되면 제어권은 이 메서드의 호출자인 Main 메서드로 돌아갑니다. MainDownloadDocsMainPageAsync가 수행하는 비동기 작업의 결과가 필요해질 때까지 계속 실행되고요. <System.Net.Http.HttpClient.GetByteArrayAsync*>가 모든 바이트를 받아오면, DownloadDocsMainPageAsync 메서드의 나머지 부분이 이어서 실행됩니다. 그다음엔 Main 메서드의 나머지 부분이 실행되죠.

:::code language="csharp" source="snippets/shared/AwaitOperator.cs":::

await 식의 피연산자는 작업이 완료되었음을 알려줄 수 있어야 해요. 일반적으로 작업이 성공하든 실패하든 완료되면 대리자(delegate)가 호출되죠. 이러한 알림이 어떻게 구현되는지에 대한 세부 내용은 C# 언어 사양await 식 섹션에 나와 있어요.

위 예시는 async Main 메서드를 사용합니다. 자세한 내용은 아래 Main 메서드에서의 await 연산자 섹션을 참고해요.

[!NOTE] 비동기 프로그래밍 입문을 원한다면 async 및 await를 사용한 비동기 프로그래밍을 보세요. asyncawait를 쓴 비동기 프로그래밍은 작업 기반 비동기 패턴 (TAP)을 따릅니다.

await 연산자는 async 키워드로 수정된 메서드, 람다 식, 또는 익명 메서드 안에서만 사용할 수 있어요. async 메서드 안에서도 동기 로컬 함수의 본문이나 lock 문의 블록 안에서는 await를 쓸 수 없습니다. 이전 언어 버전에서는 unsafe 컨텍스트에서도 await를 사용할 수 없었는데요, C# 15 미리 보기의 메모리 안전 변경 사항에서 unsafe 컨텍스트에서도 await가 허용됐어요. 남은 제한은 fixed의 본문이나 이니셜라이저에서 await를 쓸 수 없다는 것뿐입니다. 그 규칙과 관련 진단에 대해서는 unsafe 코드 구조의 오류 및 경고 해결을 참고해요.

await 연산자의 피연산자는 대개 다음 .NET 유형 중 하나예요: xref:System.Threading.Tasks.Task, xref:System.Threading.Tasks.Task`1, xref:System.Threading.Tasks.ValueTask, xref:System.Threading.Tasks.ValueTask`1. 다만 awaitable한 식이라면 무엇이든 await 연산자의 피연산자가 될 수 있습니다. 자세한 내용은 C# 언어 사양Awaitable expressions 섹션을 보세요.

await t 식의 타입은 t의 타입이 xref:System.Threading.Tasks.Task`1 또는 xref:System.Threading.Tasks.ValueTask`1일 때 TResult예요. t의 타입이 xref:System.Threading.Tasks.Task 또는 xref:System.Threading.Tasks.ValueTask라면 await t의 타입은 void입니다. 두 경우 모두에서 t가 예외를 던지면 await t는 그 예외를 다시 던집니다.

비동기 스트림과 disposable

await foreach 문을 쓰면 비동기 데이터 스트림을 소비할 수 있어요. 자세한 내용은 반복 문 문서의 foreach 섹션을 참고하고요.

await using 문은 비동기적으로 해제 가능한(disposable) 객체, 즉 xref:System.IAsyncDisposable 인터페이스를 구현하는 타입의 객체를 다룰 때 사용합니다. 자세한 내용은 DisposeAsync 메서드 구현 문서의 Using async disposable 섹션을 보세요.

Main 메서드에서의 await 연산자

Main 메서드는 애플리케이션의 진입점 역할을 해요. MainTaskTask<int>를 반환할 수 있는데, 이렇게 되면 async로 동작할 수 있죠. Main 메서드를 async로 만들면 그 본문에서 await 연산자를 쓸 수 있습니다. 이전 C# 버전에서는 Main 메서드가 비동기 작업의 완료를 기다리게 하려면, 해당 async 메서드가 반환하는 xref:System.Threading.Tasks.Task`1 인스턴스의 xref:System.Threading.Tasks.Task`1.Result?displayProperty=nameWithType 속성 값을 가져와야 했어요. 값을 만들지 않는 비동기 작업이라면 xref:System.Threading.Tasks.Task.Wait*?displayProperty=nameWithType 메서드를 호출하고요. 언어 버전을 선택하는 방법은 C# 언어 버전 관리를 참고해요.

C# 언어 사양

더 자세한 내용은 C# 언어 사양Await expressions 섹션을 확인해요.

더 알아보기