Search

Asynchronous programming techniques

수십 년 동안 개발자들은 응용 프로그램이 블로킹되는 것을 방지하는 문제에 직면해왔다.
데스크탑, 모바일, 심지어 서버 사이드 응용 프로그램을 개발할 때도 우리는 사용자가 기다리도록 하거나 더 나아가 응용 프로그램의 확장을 저해하는 병목 현상을 피하고자 한다.

다양한 비동기 솔루션

Threading

스레드는 별도의 실행 흐름을 생성하여 비동기 작업을 수행한다.
이를 통해 메인 스레드를 블로킹하지 않고 작업을 처리할 수 있다.
fun postItem(item: Item) { val token = preparePost() val post = submitPost(token, item) processPost(post) } fun preparePost(): Token { // 요청을 수행하고 consequently 메인 스레드를 블로킹합니다. return token }
Kotlin
복사
설명
preparePost가 오랜 시간이 걸리는 프로세스라고 가정할 때 이는 사용자 인터페이스를 블로킹하게 된다.
이를 피하기 위해 별도의 스레드에서 실행할 수 있다.
단점
스레드는 비용이 많이 드는 컨텍스트 스위치를 요구한다.
스레드를 생성할 수 있는 수가 기본 운영 체제에 의해 제한되므로 무한하지 않고 병목 현상을 유발할 수 있다.
JavaScript 같은 일부 플랫폼은 지원하지 않으므로 항상 사용 가능한 것이 아니다.
멀티 스레드 프로그래밍에서 디버깅과 레이스 컨디션을 피하는 것이 항상 문제이기 때문에 쉽지 않다.

Callbacks

콜백은 하나의 함수를 다른 함수의 인자로 전달하고, 특정 작업이 완료되면 해당 콜백을 호출하는 방식이다.
fun postItem(item: Item) { preparePostAsync { token -> submitPostAsync(token, item) { post -> processPost(post) } } } fun preparePostAsync(callback: (Token) -> Unit) { // 요청을 수행하고 즉시 반환합니다. // 나중에 호출될 콜백을 준비합니다. }
Kotlin
복사
설명
preparePostAsync 는 비동기적으로 요청을 수행하고 결과를 콜백으로 반환한다.
단점
콜백으로 사용되는 함수는 종종 자체 콜백이 필요하게 되며 중첩된 콜백으로 인해 코드가 복잡해지는 원인이 된다.
중첩 구조는 오류 처리가 복잡하고 전파하기 어렵게 만든다.

Futures, promises, and others

퓨처와 프로미스는 비동기 작업의 결과를 나타내는 객체로, 나중에 사용할 수 있는 값을 약속한다.
주로 체이닝 방식으로 작업을 수행한다.
fun postItem(item: Item) { preparePostAsync() .thenCompose { token -> submitPostAsync(token, item) } .thenAccept { post -> processPost(post) } } fun preparePostAsync(): Promise<Token> { // 요청을 수행하고 나중에 완료되는 프로미스를 반환합니다. return promise }
Kotlin
복사
설명
preparePostAsync 는 프로미스를 반환하고 이후의 작업은 체이닝을 통해 이어진다.
단점
프로그래밍 모델이 상향식 명령형 접근 방식에서 콜백과 유사한 체인 호출로 구성된 모델로 변경된다.
일반적으로 thenCompose 또는 thenAccept 와 같은 완전히 새로운 API를 배워야 한다.
반환 타입이 Promise라는 새로운 타입으로 변경된다.
오류의 전파 및 체인이 간단하지 않다.

Reactive Extensions

Rx는 데이터를 스트림으로 생각하며 이러한 스트림을 관찰할 수 있는 observable streams로 이동한다.
실제로 Rx는 데이터 작업을 가능하게 하는 일련의 확장을 가진 Observer 패턴이라고 할 수 있다.
접근 방식은 Futures와 유사하지만 Future는 개별 요소를 반환하는 반면 Rx는 스트림을 반환한다.
fun postItem(item: Item) { Observable.just(item) .map { preparePostAsync() } .flatMap { token -> submitPostAsync(token, item) } .subscribe { post -> processPost(post) } }
Kotlin
복사
설명
Observable 을 사용하여 데이터 스트림을 생성하고 이를 통해 비동기적으로 작업을 처리한다.
단점
기존 프로그래밍 모델에서 스트림 기반 모델로의 전환이 필요하다.
API와 개념 학습에 시간이 소요된다.
스트림 처리의 비동기적 특성으로 인해 디버깅이 어렵다.

Corutines

코루틴은 함수가 일시 중지(suspend)하고 나중에 재개(resume)할 수 있는 구조이다.
fun postItem(item: Item) { launch { val token = preparePost() val post = submitPost(token, item) processPost(post) } } suspend fun preparePost(): Token { // 요청을 수행하고 코루틴을 일시 중지합니다. return suspendCoroutine { /* ... */ } }
Kotlin
복사
설명
preparePost는 일시 중지 가능한 함수로 suspend 라는 키워드가 붙는다.
이는 함수가 실행되고 실행을 일시 중지한 후 나중에 다시 재개될 수 있음을 의미한다.
코드는 여전히 동기식 코드처럼 위에서 아래로 작성되며 특별한 구문을 사용할 필요 없이 launch 라는 함수를 통해 코루틴을 시작한다.