•
수십 년 동안 개발자들은 응용 프로그램이 블로킹되는 것을 방지하는 문제에 직면해왔다.
•
데스크탑, 모바일, 심지어 서버 사이드 응용 프로그램을 개발할 때도 우리는 사용자가 기다리도록 하거나 더 나아가 응용 프로그램의 확장을 저해하는 병목 현상을 피하고자 한다.
다양한 비동기 솔루션
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 라는 함수를 통해 코루틴을 시작한다.