코루틴 빌더가 뭔데
중단 함수는 일반 함수를 호출 가능하지만, 일반 함수는 중단 함수를 호출할 수 없다.
모든 중단 함수는 또 다른 중단 함수에 의해 호출되어야 하는데 결국 그 앞엔 시작점이 있을 것이다.
코루틴 빌더가 그 역할을 한다.
즉, 코루틴을 생성하는 메서드인 것이다.
코루틴 빌더의 종류
- launch
- runBlocking
- async
각각 다른 쓰임새가 있다 천천히 알아보자.
launch
launch의 작동 방식은 thread 함수를 호출하여 새로운 스레드를 시작하는 것과 비슷하다.
불꽃놀이를 할 때 각각의 불꽃이 하늘 위로 각자 퍼지는 것처럼 별개로 실행된다.
다음 예제에서는 '슛'이 실행되고 1초 후 '펑'들이 실행되는 것을 볼 수 있다.
여기서 `Thread.sleep` 을 하는 이유는, 메인 함수가 코루틴이 일 할 기회조차 주지 않고 코루틴을 중단시켜버린다.
fun main() {
GlobalScope.launch {
delay(1000L)
println("펑")
}
GlobalScope.launch {
delay(1000L)
println("펑")
}
GlobalScope.launch {
delay(1000L)
println("펑")
}
println("슛")
Thread.sleep(2000L)
}
launch 가 작동하는 방식은 *데몬 스레드와 비슷하지만 훨씬 가볍다.
* 데몬 스레드: 백그라운드에서 돌아가며, 우선순위가 낮은 스레드. launch와 데몬스레드는 프로그램이 끝나는 것을 막을 수 없음.
그럼 중단을 허용하고 싶으면 어떻게 하지..
runBlocking
코루틴이 스레드를 블로킹하지 않고 작업을 중단시키기만 하는 것이 일반적인 법칙이다. 하지만 블로킹이 필요한 경우도 있다. 위에서 봤듯이 프로그램을 너무 빨리 끝내지 않기 위해 스레드를 블로킹하고 싶을 때 사용하면 된다.
다음 예제는 runBlocking의 실행 예제다 결과 화면은 이해를 돕기 위해 걸리는 시간과 같이 출력해 보았다.
fun main() {
runBlocking {
delay(1000L)
println("하나")
}
runBlocking {
delay(1000L)
println("둘")
}
runBlocking {
delay(1000L)
println("셋")
}
println("시작")
}
runBlocking이 사용되는 특수한 경우는
1) 프로그램이 끝나는 걸 방지하기 위해 스레드를 블로킹할 필요가 있는 메인 함수
2) 같은 이유로 스레드를 블로킹할 필요가 있는 유닛 테스트
가 있다.
async
async는 launch와 비슷하지만 값을 생성하도록 설계되어 있다. 이 값은 람다 표현식에 의해 반환되어야 한다
async 함수를 자세히 살펴보면, Deferred <T> 타입의 객체를 리턴한다. 여기서 T는 생성되는 값의 타입일 것이다.
Deferred 도 보면, 작업이 끝나면 값을 반환하는 중단 메서드인 await가 있다.
그럼 다음 예제를 살펴보면, asyncResult는 'Deferred <Int>' 타입을 갖게 될 것이고 awaitResult는 Int 타입을 갖게 될 것이다.
fun main() = runBlocking {
val asyncResult: Deferred<Int> = GlobalScope.async {
delay(1000L)
7
}
val awaitResult: Int = asyncResult.await()
println("결과: $awaitResult")
}
launch 빌더와 비슷하게 async 빌더는 호출되자마자 코루틴을 즉시 시작한다. 그래서 몇 개의 작업을 한 번에 시작하고 모든 결과를 한꺼번에 기다릴 때 사용한다. 반환된 Deferred는 값이 생성되면 해당 값을 내부에 저장하기 때문에 'await'에서 값이 반환되는 즉시 값을 사용할 수 있다. 하지만 값이 생성되기 전에 await를 호출하면 값이 나올 때까지 기다리게 된다.
다음 예제와 결과를 보면 이해가 가는군요. 이해가 쉽게 결과에는 옆에 실행 시간도 같이 표기했다.
fun main() = runBlocking {
val asyncResult = GlobalScope.async {
delay(1000L) // 1초
"저요"
}
val asyncResult2 = GlobalScope.async {
delay(3000L) // 3초
"저요2"
}
val asyncResult3 = GlobalScope.async {
delay(2000L) // 2초
"저요3"
}
println("결과1: ${asyncResult.await()}")
println("결과2: ${asyncResult2.await()}")
println("결과3: ${asyncResult3.await()}")
}
async는 값을 생성할 때,
launch는 값이 필요 없을 때 써야 한다!
구조화된 동시성
코루틴 빌더 정의 살펴보기
위에 코루틴 빌더를 설명할 때 뭔가 걸리는 게 있다. runBlockin은 그냥 사용하는데, 왜 launch와 async는 GlobalScope가 필요했을까?
일단 각 코루틴 빌더를 다시 살펴보자..
launch와 async는 CoroutineScope의 확장 함수다. 그리고 runBlocking의 block 파라미터가 리시버 타입이 CoroutineScope인 함수형 타입이다.
그럼 runBlocking안에 this.launch나 this.async로 호출이 가능하고, 이것들은 runBlocking의 자식이 된다. 부모가 자식들 모두를 기다리는 것은 당연한 일이기에, runBlocking은 모든 자식이 작업을 끝마칠 때까지 중단된다.
부모는 자식들을 위한 스코프를 제공하고, 자식들을 해당 스코프 내에서 호출한다. 그럼 구조화된 동시성이라는 관계가 성립한다.
구조화된 동시성의 특징
- 자식은 부모로부터 콘텍스트를 상속받는다.
- 부모는 모든 자식이 작업을 마칠 때까지 기다린다.
- 부모 코루틴이 취소되면 자식 코루틴도 취소된다.
- 자식 코루틴에서 에러가 발생하면, 부모 코루틴 또한 에러로 소멸한다.
3줄 요약
1. 코루틴 빌더는 코루틴을 생성하는 메서드이다.
2. 코루틴 빌더는 launch , runBlocking, async 가 있다.
3. 코루틴스코프는 구조화된 동시성을 가진다.
참고자료
마르친 모스카와(2023), 코틀린 코루틴 Kotlin Coroutines : Deep Dive - 안드로이드 및 백엔드 개발자를 위한 비동기 프로그래밍, 인사이트
https://kotlinlang.org/docs/coroutines-basics.html#scope-builder
'Kotlin Coroutines' 카테고리의 다른 글
[Kotlin Coroutines] 디스패처 (1) | 2024.05.27 |
---|---|
[Kotlin Coroutines] 코루틴 스코프 함수 (0) | 2024.05.20 |
[Kotlin Coroutines] 코루틴 예외 처리 (0) | 2024.05.17 |
[Kotlin Coroutines] 코루틴 취소 (0) | 2024.05.17 |
[Kotlin Coroutines] Job 과 자식 코루틴 기다리기 (0) | 2024.05.16 |