C# 비동기 프로그래밍
C#의 async와 await 키워드는 비동기 작업을 다루는 과정을 간소화하여 비동기 코드를 더 읽기 쉽고 유지보수하기 쉽게 만드는 데 사용된다. 비동기 프로그래밍은 시간이 많이 걸리는 작업(예: I/O 작업, 웹 요청, 데이터베이스 트랜잭션 등)을 메인 스레드를 차단하지 않고 실행할 수 있게 해주므로, 사용자 인터페이스의 반응성을 유지하고 서버 측 응용 프로그램의 확장성을 향상시키는 데 필수적이다.
async와 await
async 키워드는 메소드, 람다 표현식 또는 익명 메소드를 비동기적으로 표시하는 데 사용된다. async 메소드는 호출자의 스레드를 차단하지 않고 잠재적으로 오래 걸리는 작업을 수행하는 편리한 방법을 제공한다. async 메소드는 await 표현식에 도달할 때까지 동기적으로 실행되며, 그 시점에서 호출자에게 제어를 반환하고 대기 중인 작업이 완료될 때까지 기다린다.
await 키워드는 비동기 메소드 내의 작업에 적용되어 대기 중인 작업이 완료될 때까지 메소드의 실행을 일시 중지한다. Task는 진행 중인 작업을 나타낸다. await 연산자는 메소드가 실행 중인 스레드를 차단하지 않는다. 대신, 컴파일러는 대기 중인 작업에 메소드의 나머지 부분을 연속으로 등록하고 즉시 호출자에게 반환한다.
async와 await 작동 방식은 다음과 같다.
async 메소드가 호출되면, 호출자에게 즉시 Task(또는 메소드가 값을 반환하는 경우 Task<T>)를 반환한다. Task는 진행 중인 작업을 나타낸다.(C++의 Future와 대응되는 개념) async 메소드 내에서 대기 중인 작업에 await 연산자가 적용되고, 대기 중인 작업이 완료될 때까지 메소드의 실행이 일시 중지된다. 작업이 완료되면, await 지점부터 실행이 재개된다.
여기에서 주목할 점은 await 시에 스레드가 차단되지 않는다는점이다. 비차단의 장점을 이해하기 위해 일반적인 멀티스레딩 비동기 방식에서부터 얘기를 시작해보자. 여러개의 스레드를 생성하고 IO 바운드 되어있는 작업을 비동기로 처리하도록 하는 경우 이 작업을 맡은 스레드는 IO작업이 끝날 때까지 차단될 것이다. 물론 이 스레드가 차단되더라도 다른 스레드들이 남아있기 때문에 여전히 멀티스레딩의 장점은 존재한다. 하지만 이런 IO 작업이 무수히 많아지는 경우, 스레드도 무수히 많이 생성하는것은 비효율적이다. 반면, await은 코드가 해당 지점에서 대기하면서 동시에 스레드를 차단하지 않기 때문에, 적당한 수의 스레드만 생성하더라도 많은 IO 작업들을 다룰 수 있다. 이것이 비차단의 장점이다.
이 예시에서, GetWebPageContentAsync는 웹 페이지의 내용을 비동기적으로 검색하는 async 메소드이다. await 키워드는 스레드를 차단하지 않고 작업이 완료될 때까지 비동기적으로 대기하는 데 사용된다. 작업이 완료되면, await 지점부터 메소드가 계속된다.
public async Task<string> GetWebPageContentAsync(string url)
{
using (HttpClient client = new HttpClient())
{
string content = await client.GetStringAsync(url); // 웹 페이지의 내용을 비동기적으로 가져온다.
return content; // 위의 비동기 작업이 완료되면 메소드의 실행이 이 지점에서 재개된다.
}
// 여기서 예외를 잡을 필요가 없다면 호출 메소드에서 처리하면 된다.
}
async, await의 장점
응답성 향상
UI 애플리케이션에서 비동기 작업을 사용하면 긴 작업(예: 네트워크 요청, 파일 I/O 작업)이 UI 스레드를 차단하지 않기 때문에 애플리케이션이 더 반응적으로 유지된다. 사용자는 작업이 백그라운드에서 수행되는 동안 인터페이스와 상호작용할 수 있다.
스케일러빌리티 향상
서버 측 애플리케이션(예: 웹 API)에서 async와 await을 사용하면 동시에 처리할 수 있는 요청의 수가 증가한다. 이는 작업이 I/O 바인딩 작업을 기다리는 동안 스레드가 차단되지 않고 다른 요청을 처리할 수 있기 때문이다.
코드 가독성 및 유지 관리
async와 await을 사용하면 전통적인 비동기 프로그래밍 모델(예: 콜백 함수 사용)에 비해 코드를 읽고 이해하기 쉽다. 비동기 코드가 동기 코드와 유사하게 보이고 작성될 수 있어, 코드의 가독성과 유지 관리가 쉬워진다.
전통적인 비동기 프로그래밍 모델(콜백함수 사용)에서는 비동기 함수를 작성하기 위해서 콜백함수를 새롭게 정의해야하고, 이는 중첩된 콜백 구조를 만들기 때문에 코드의 가독성을 저하시킨다.
하지만 async, await을 이용한다면 다르다. async 키워드를 통해 이 함수가 비동기 루틴을 이용한다는 사실을 쉽게 파악할 수 있고, await 키워드를 통해 비동기 처리 루틴들을 순차적으로 표현할 수 있는데, 이는 거의 동기 코드와 비슷할 정도로 쉽게 표현될 수 있다.
자원 사용 최적화
비동기 프로그래밍은 필요한 경우에만 추가 스레드를 사용하므로 자원 사용이 최적화된다. 이는 특히 I/O 바운드 작업에서 중요한데, 이러한 작업은 CPU 시간보다는 대기 시간이 많이 필요하기 때문이다.
간결한 예외 처리
async 메소드에서 발생한 예외는 await 표현식을 사용하는 호출 코드에서 캐치할 수 있으므로, 예외 처리 로직을 더 간결하게 유지할 수 있다.
비동기 작업의 조합
async와 await을 사용하면 여러 비동기 작업을 쉽게 조합하고 조정할 수 있다. 예를 들어, Task.WhenAll 메소드를 사용하여 여러 비동기 작업이 모두 완료될 때까지 기다릴 수 있으며, 이는 복잡한 비동기 로직을 구현할 때 유용하다.
컨텍스트 전환 최소화
ConfigureAwait(false)를 사용함으로써 UI 스레드와 같은 특정 컨텍스트로의 불필요한 전환을 방지할 수 있어, 성능을 향상시킬 수 있다. 이는 라이브러리나 백그라운드 작업에서 특히 유용하다.
참고
https://www.reddit.com/r/AskProgramming/comments/15wf5tl/c_asyncawait_vs_background_threads/
https://blog.stephencleary.com/2013/11/there-is-no-thread.html
'프로그래밍 > C#' 카테고리의 다른 글
[C#] Nullable 타입 (0) | 2024.03.07 |
---|---|
[C#] 불변성 처리 (0) | 2024.03.05 |