async
와 await
는 비동기 프로그래밍을 보다 손쉽게 지원하기 위해 C# 5.0에 추가된 중요한 기능이다.
async
는 메서드를 정의할 때 추가하는 키워드로, 해당 함수가 비동기 함수이며 메서드 안에 await
연산자가 존재함을 알린다. await
연산자는 1개 이상 존재할 수 있고 하나도 없을 수도 있지만, 없을 때는 경고 메시지를 출력한다. async
를 표시한다고 해서 자동으로 비동기 방식으로 프로그램을 수행하는 것은 아니고, 일종의 보조 역할을 하는 컴파일러 지시어이다.
async
메서드의 리턴 타입은 대부분 리턴 값이 있으면 Task<TResult>
, 없으면 Task
타입인데, 리턴 시 포함하고자 하는 데이터가 String
타입이라면 async Task<String> method() {...}
와 같이 정의하고 return "문자열";
과 같이 반환하고 싶은 데이터만 반환한다. C# 컴파일러는 반환문의 문자열을 자동으로 Task<String>
으로 변환해준다. 또는 async
메서드의 리턴 타입으로 이벤트핸들러를 위한 void
타입을 사용하기도 한다.
메서드 앞의 async
한정자를 확인하면 컴파일러는 메서드 내 await
연산자를 찾는다. 그리고 컴파일러가 그곳에서 메서드를 호출한 호출자에게 제어를 돌려주도록 실행 파일을 만든다. 피연산자의 작업을 비동기로 실행하면서, 해당 피연산자의 비동기 작업이 마무리될 때까지 제어권을 얻은 호출자가 계속 실행될 수 있게 하는 것이다. 비동기 작업이 마무리되면 await
연산자가 해당 작업의 결과를 평가하면서 다음 라인부터 실행을 재개한다.
await
의 피연산자로는 Task
또는 Task<T>
객체를 사용한다. 반드시 해당 타입일 필요는 없지만, 내부적으로 반드시 호출해야 하는 GetAwaiter()
메서드를 호출하는 타입이 피연산자여야 한다. 피연산자는 쓰레드 풀에 큐잉되며, 호출자에게 제어를 넘기고 쓰레드 풀 내의 쓰레드에게서 피연산자가 실행되도록 한다. 이때 await
연산자는 현재 피연산자의 비동기 실행이 종료된 후 다음 문장을 이어서 실행하도록 해주는데, 마치 ContinueWith()
메서드로 연결된 것과 같은 방식으로 동작한다.
비동기 함수를 사용할 때 다음과 같은 제한이 있음을 알아 두어야 한다.
Main()
메서드, 생성자, 프로퍼티, 이벤트 접근자 메서드는 비동기 함수가 될 수 없다.out
, ref
매개변수를 가질 수 없다.catch
, finally
, unsafe
블록 내에서는 await
연산자를 사용할 수 없다.await
연산자 앞쪽에서 쓰레드의 소유권 및 재귀호출을 허용하는 락을 획득했다가 await
후에 이를 해제할 수 없다. await
이전에 실행되던 쓰레드와 await
이후에 실행되는 쓰레드가 같을 것이란 보장이 없기 때문이다.from
절의 첫 번째 컬렉션을 나타내는 부분이나 join
절의 컬렉션을 나타내는 부분에서만 await
연산자를 사용할 수 있다.