안드로이드 개발자 노트
[코루틴의 정석] 11-2, 11-3장. 코루틴 심화 본문
11.2. CoroutineStart의 다양한 옵션들 살펴보기
코루틴 빌더 함수의 start 인자로 받는 CoroutineStart에 대해 다뤄본다.
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
- CoroutineStart.DEFAULT
- CoroutineStart.ATOMIC
- CoroutineStart.UNDISPATCHED
11.2.1. CoroutineStart.DEFAULT
CoroutineStart.DEFAULT는 코루틴 빌더의 기본 실행 옵션이며, 코루틴 빌더 함수를 호출한 즉시 생성된 코루틴의 실행을 CoroutineDispatcher 객체에 예약한다.
fun main() = runBlocking<Unit> {
launch {
println("작업1")
}
println("작업2")
}
/*
// 결과:
작업2
작업1
*/
11.2.2. CoroutineStart.ATOMIC
코루틴이 실행 요청됐지만 CoroutineDispatcher 객체가 사용할 수 있는 스레드가 모두 작업 중이어서 스레드로 보내지지 않는 경우 생성 상태에 머무는데 이를 실행 대기 상태라고 한다.
CoroutineStart.ATOMIC을 적용한 후 launch 코루틴이 실행 대기 상태일 때 취소를 요청해도 취소되지 않는다.
이 옵션은 실행 대기 상태에서 취소를 방지하기 위한 옵션인 것이다.
fun main() = runBlocking<Unit> {
val job = launch(start = CoroutineStart.ATOMIC) {
println("작업1")
}
job.cancel() // 실행 대기 상태의 코루틴에 취소 요청
println("작업2")
}
/*
// 결과:
작업2
작업1
*/
11.2.3. CoroutineStart.UNDISPATCHED
일반적인 코루틴은 실행이 요청되면 CoroutineDispatcher 객체의 작업 대기열에서 대기하다가 스레드에 할당돼 실행되지만, CoroutineStart.UNDISPATCHED 옵션이 적용된 코루틴은 CoroutineDispatcher 객체의 작업 대기열을 거치지 않고 호출자의 스레드에서 즉시 실행된다.
fun main() = runBlocking<Unit> {
launch(start = CoroutineStart.UNDISPATCHED) {
println("작업1")
}
println("작업2")
}
/*
// 결과:
작업1
작업2
*/
11.3. 무제한 디스패처 (Unconfined Dispatcher)
11.3.1. 무제한 디스패처란?
무제한 디스패처는 자신을 실행시킨 스레드에서 코루틴을 즉시 실행하도록 만드는 디스패처이다.
스레드를 고정하지 않고 코루틴이 시작된 현재 스레드에서 실행하다가, 특정 시점에서 다른 스레드로 전환될 수 있으므로 스레드에 대한 제한이 없기 때문에 무제한 디스패처라는 이름이 붙었다.
11.3.2. 무제한 디스패처의 특징
11.3.2.1. 코루틴이 자신을 생성한 스레드에서 즉시 실행된다.
제한된 디스패처와 무제한 디스패처의 차이는 아래와 같다.
fun main() = runBlocking<Unit>(Dispatchers.IO) {
println("runBlocking 코루틴 실행 스레드: ${Thread.currentThread().name}") // runBlocking 코루틴이 실행되는 스레드 출력
launch(Dispatchers.Unconfined) { // Dispatchers.Unconfined를 사용해 실행되는 코루틴
println("launch 코루틴 실행 스레드: ${Thread.currentThread().name}") // launch 코루틴이 실행되는 스레드 출력
}
}
/*
// 결과:
runBlocking 코루틴 실행 스레드: DefaultDispatcher-worker-1 @coroutine#1
launch 코루틴 실행 스레드: DefaultDispatcher-worker-1 @coroutine#2
*/
runBlocking 코루틴이 실행되는 스레드에서 launch 코루틴이 무제한 디스패처를 사용해 실행되도록 하여, runBlocking이 사용하던 스레드에서 즉시 코루틴이 실행된다. 이는 CoroutineStart.UNDISPATCHED 옵션을 적용했을 때의 동작과 비슷하다. 즉, 무제한 디스패처를 사용해 실행되는 코루틴은 스레드 스위칭 없이 즉시 실행된다.
11.3.2.2. 중단 시점 이후의 재개는 코루틴을 재개하는 스레드에서 한다.
무제한 디스패처를 사용해 실행되는 코루틴은 자신을 실행시킨 스레드에서 스레드 스위칭 없이 즉시 실행된다.
무제한 디스패처를 사용하는 코루틴이 일시 중단 후 재개된다면 자신을 재개시키는 스레드에서 실행된다.
fun main() = runBlocking<Unit> {
launch(Dispatchers.Unconfined) {
println("일시 중단 전 실행 스레드: ${Thread.currentThread().name}")
delay(100L)
println("일시 중단 후 실행 스레드: ${Thread.currentThread().name}")
}
}
/*
// 결과:
일시 중단 전 실행 스레드: main
일시 중단 후 실행 스레드: kotlinx.coroutines.DefaultExecutor
*/
DefaultExcecutor 스레드는 delay 함수를 실행하는 스레드이다.
무제한 디스패처는 어떤 스레드가 코루틴을 재개시키는지 예측하기 어렵기 때문에 일반적인 상황에서 사용하면 비동기 작업이 불안정해지며, 테스트 등의 특수한 상황에서만 사용하도록 한다.
CoroutineStart.UNDISPATCHED와 CoroutineStart.UNCONFINED의 차이
두 CoroutineStart는 호출자의 스레드에서 즉시 실행되는 공통점이 있지만 무제한 디스패처(CoroutineStart.UNCONFINED)는 중단 시점 이후 코루틴을 재개시킨 스레드에서 실행된다는 차이가 있다.
11.4. 코루틴의 동작 방식과 Continuation
11.4.1. Continuation Passing Style
코루틴은 일시 중단을 하고 재개하기 위해서 코루틴의 실행 정보를 저장하고 전달한다.
이러한 방식을 CPU(Continuation Passing Style)라고 하며, 이어서 실행해야 하는 작업을 Continuation 객체를 사용하여 전달한다.
Continuation 객체는 코루틴의 일시 중단 지점에 코루틴의 실행 상태를 저장하며, 다음에 실행해야 할 작업에 대한 정보가 포함되어있어 코루틴 재개 시 이를 통해 코루틴의 상태를 복원하고 이어서 작업을 진행할 수 있다.
11.4.2. 코루틴의 일시 중단과 재개로 알아보는 Continuation
일시 중단 시점의 실행 정보를 저장하는 Continuation 객체를 CancellableContinuation 타입으로 제공하는 suspendCancellableCoroutine 함수를 사용하면 Continuation 객체의 정보를 살펴볼 수 있다.
fun main() = runBlocking<Unit> {
println("runBlocking 코루틴 일시 중단 호출")
suspendCancellableCoroutine<Unit> { continuation: CancellableContinuation<Unit> ->
println("일시 중단 시점의 runBlocking 코루틴 실행 정보: ${continuation.context}")
continuation.resume(Unit) // 코루틴 재개 호출
}
println("runBlocking 코루틴 재개 후 실행되는 코드")
}
/*
// 결과:
runBlocking 코루틴 일시 중단 호출
일시 중단 시점의 runBlocking 코루틴 실행 정보: [BlockingCoroutine{Active}@551aa95a, BlockingEventLoop@35d176f7]
runBlocking 코루틴 재개 후 실행되는 코드
Process finished with exit code 0
*/
코루틴에서 일시 중단이 일어나면 Continuation 객체에 실행 정보가 저장되며, 일시 중단된 코루틴은 Continuation 객체에 대해 resume 함수가 호출돼야 재개된다. resume 함수가 호출되지 못하면 코루틴이 재개되지 않아 일시 중단된 상태로 멈춰있는다.
delay 일시 중단 함수에서도 이와 비슷하게 코루틴을 일시 중단하고 특정 시점 이후에 복구되도록 만든다.
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
// if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
if (timeMillis < Long.MAX_VALUE) {
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
}
11.4.3. 다른 작업으로부터 결과 수신해 코루틴 재개하기
코루틴 재개 시 다른 작업으로부터 결과를 수신받아야 하는 경우에는 suspendCancellableCoroutine 함수의 타입 인자에 결과로 반환받는 타입을 입력하면 된다.
fun main() = runBlocking<Unit> {
println("runBlocking 코루틴 일시 중단 호출")
val result = suspendCancellableCoroutine<String> { continuation: CancellableContinuation<String> ->
println("일시 중단 시점의 runBlocking 코루틴 실행 정보: ${continuation.context}")
continuation.resume(value ="실행 결과", onCancellation = null) // onCancellation은 코루틴이 취소될 때 호출되는 콜백 함수이다.
}
println("runBlocking 코루틴 재개 후 실행되는 코드: $result")
}
/*
// 결과:
runBlocking 코루틴 일시 중단 호출
일시 중단 시점의 runBlocking 코루틴 실행 정보: [BlockingCoroutine{Active}@551aa95a, BlockingEventLoop@35d176f7]
runBlocking 코루틴 재개 후 실행되는 코드: 실행 결과
Process finished with exit code 0
*/
'Kotlin > 코루틴의 정석' 카테고리의 다른 글
[코루틴의 정석] 12장. 코루틴 단위 테스트 (1) | 2024.10.06 |
---|---|
[코루틴의 정석] 11-1장. 코루틴 심화 (0) | 2024.10.01 |
[코루틴의 정석] 10장. 코루틴의 이해 (1) | 2024.09.28 |