본문 바로가기

코루틴

[코루틴] 코루틴의 동작 방식

 

코루틴 동작 방식

코루틴은 일시 중단이 가능하고 필요한 시점에 다시 실행을 재개할 수 있는 기능을 가진다

일시 중단과 재개를 위해 코루틴의 실행 정보 저장이 필요

 

Continuation Passing Style (CPS)

continuation(이어서 실행해야 하는 작업)을 전달하는 스타일의 방식

함수는 결과를 직접 반환하는 대신, 결과를 처리할 다음 단계(continuation)를 인자로 받고

실행이 완료되면 continuation을 호출하여 다음 단계 시작

// CPS 스타일
fun CpsFunction(arg, (result) -> { /* next 작업 */ }) 

// 일반 함수
fun generalFunction(arg)

 

// compile 시 cps 스타일로 변경되는 suspend
suspend fun doSomething(param: Int): String {
    return ""
}

@Nullable
public static final Object doSomething(int param, @NotNull Continuation $completion) {
    return "";
}

 

 

Continuation

일시 중단 시점에 코루틴의 실행 상태 저장

다음에 실행해야 할 작업에 대한 정보를 포함

resume 함수가 호출되어야 재개

// 일시 중단 지점 후 이어서 실행해야 하는 작업을 나타내는 Interface, T 타입 value 반환
@SinceKotlin("1.3")
public interface Continuation<in T> {
    // continuation에 해당하는 coroutine context
    public val context: CoroutineContext

    // 성공이나 실패 결과를 마지막 일시 중단 지점의 반환 값으로 전달하여 해당 코루틴의 실행을 재개
    public fun resumeWith(result: Result<T>)
}

 

 

Resume을 호출하지 않는 일시 중단의 경우

fun main() = runBlocking<Unit> {
    println("runBlocking 코루틴 일시 중단 호출")
    // 실행 정보가 continuation에 저장되어 제공
    suspendCancellableCoroutine<Unit> { continuation: CancellableContinuation<Unit> ->
        println("일시 중단 시점의 runBlocking 코루틴 정보 : ${continuation.context}")
    }
    println("일시 중단된 코루틴이 재개되지 않아 실행되지 않는 코드")
}
// 결과 (실행이 종료되지 않음)
runBlocking 코루틴 일시 중단 호출
일시 중단 시점의 runBlocking 코루틴 정보 : [BlockingCoroutine{Active}@5f8ed237, BlockingEventLoop@2f410acf]

// 현재 실행 중인 코루틴(여기서는 runBlocking)을 일시 중단
// 코루틴을 중단시키고, 제공된 람다 함수를 즉시 실행
public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T

 

 

Resume을 호출하는 일시 중단

fun main() = runBlocking<Unit> {
    println("runBlocking 코루틴 일시 중단 호출")
    suspendCancellableCoroutine<Unit> { continuation: CancellableContinuation<Unit> ->
        println("일시 중단 시점의 runBlocking 코루틴 정보 : ${continuation.context}")
        // 코루틴 resume
        continuation.resume(Unit)
    }
    println("코루틴 재개 후 실행되는 코드")
}
// 결과
runBlocking 코루틴 일시 중단 호출
일시 중단 시점의 runBlocking 코루틴 정보 : [BlockingCoroutine{Active}@5f8ed237, BlockingEventLoop@2f410acf]
코루틴 재개 후 실행되는 코드

 

 

delay를 사용한 일시 중단

fun main() = runBlocking<Unit> {
    println("runBlocking 코루틴 일시 중단 호출")
    delay(1000L)
    println("delay 이후 다시 실행되는 코드")
}

// 내부적으로 resume 호출
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)
        }
    }
}