관리 메뉴

오늘도 더 나은 코드를 작성하였습니까?

3장 중단은 어떻게 작동할까? 본문

Coroutine/코틀린 코루틴

3장 중단은 어떻게 작동할까?

hik14 2025. 11. 27. 01:00

중단

- 코틀린 코루틴의 다른 모든 개념의 기초가 되는 필수적인 요소

 

코루틴을 중단한다는건 실행을 중간에 멈추는것을 의미한다.

코루틴은 중단하였을때 continuation객체를 반환하고, continuation을 이용해서 중단된 곳에서 다시 시작할 수있다.

 

재개

중단 함수는 말 그대로 코루틴을 중단할 수 있는 함수입니다. 이는 중단 함수가 반드시 코루틴(또는 다른 중단 함수)에 의해 호출되어야 함을 의미한다.

suspend fun main() {
    println("before")

    println("after")
}

// before
// after

 

suspendCoroutine 함수를 이용하여 before, after출력 사이에서 중단해 보기

 

import kotlin.coroutines.suspendCoroutine

suspend fun main() {
    println("before")

    suspendCoroutine<Unit> {  }

    println("after")
}

// before

 

suspendCoroutine 

- 람다를 인자로 받고, 람다는 중단되기 전에 실행됨.

- 람다인자는 continuation을 인자로 받음.

- continuation 저장한뒤 코루틴을 다시 실행할 시점을 결정하기 위해 사용됨.

mport kotlin.coroutines.suspendCoroutine

suspend fun main() {
    println("before")

    suspendCoroutine<Unit> { continuation ->
        println("before too")
    }

    println("after")
}

// before
// before too (프로그램 계속 실행됨)

 

continuation을 이용해서 바로 중단된 함수를 재게해보기.

resume 호출.

import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

suspend fun main() {
    println("before")

    suspendCoroutine<Unit> { continuation ->
        continuation.resume(Unit)
    }

    println("after")
}

 

suspendCoroutine 안에서 잠깐 정지(sleep)한 뒤 재개되는 다른 스레드를 호출

import kotlin.concurrent.thread
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

suspend fun main() {
    println("Before")

    suspendCoroutine<Unit> { continuation ->
        thread { 
            println("Suspended")
            Thread.sleep(1000)
            continuation.resume(Unit)
            println("Resumed")
        }
    }

    println("After")
}

// Before
// Suspended
// (1초 후)
// After
// Resumed

 

다른 스레드가 재게하는 방식 - 정해진 시간뒤에 코루틴을 다시 재개하는 함수

1초 뒤에 사라지는 스레드는 불필요하기에,  ScheduledExecutor를 이용한다.

delay 함수로 추출 하기.

import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

private val executor = Executors.newSingleThreadScheduledExecutor {
    Thread(it, "scheduler").apply {
        isDaemon = true
    }
}


suspend fun delay(timeMillis: Long): Unit =
  suspendCoroutine<Unit> { continuation ->
        executor.schedule({
            continuation.resume(Unit)
        }, timeMillis, TimeUnit.MILLISECONDS)
    }


suspend fun main() {
    println("before")

   	delay(1000)

    println("after")
}

// before
// (1초 후)
// after

 

위 코드는 코틀린 라이브러리에서 delay가 구현된 방식이랑 정확히 일치합니다.

 

값으로 재게 하기

 

Unit은 함수의 반환값이며, Continuation의 제네릭 타입 인자이다.

val ret: Unit = 
	susendCoroutine<Unit> { cont: Continuation<Unit> ->
    	cont.resume(Unit)
    }
suspend fun main() {
    val i: Int = suspendCoroutine<Int> { cont ->
        cont.resume(42)
    }
    println(i)

    val str: String = suspendCoroutine<String> { cont ->
        cont.resume("some text")
    }
    println(str)

    val b: Boolean = suspendCoroutine<Boolean> { cont ->
        cont.resume(true)
    }
    println(b)
}

 

susendCoroutine은 Continuation 객체로 반환될 값의 타입을 지정하고, resume로 재개와 함께 반환되는 값은 반드시 같은 타입이다.

 

코루틴이 있으면, 중단함과 동시에
"데이터를 받은 후, 받은 데이터를 resume함수를 통해 보내줘"라고

컨티뉴에이션 객체를 통해 라이브러리에 전달한다.

 

suspend fun requestUser(): User = suspendCancellableCoroutine<User> { cont ->
    requestUser { user ->
        cont.resume(user)
    }
}

suspend fun main(){
	println("Before")
    val user = requestUser()
    println(user)
    println("After")
}

 

예외로 재게 하기

 

모든 함수는 값을 반환하거나 예외를 던진다.

resumeWithException이 호출되면, 중단된 지점에서 인자로 예외를 넣어준다.

class MyException: Throwable("Just an exception")

suspend fun main() {
    try {
        suspendCoroutine<Unit> { cont ->
            cont.resumeWithException(MyException())
        }
    } catch (e: MyException) {
        println("Caught")
    }
}

 

함수가 아니라 코루틴을 중단 시킨다

var continuation: Continuation<Unit>? = null

suspend fun suspendAndSetContinuation() {
    suspendCoroutine<Unit> { cont ->
        continuation = cont
    }
}

suspend fun main() {
    println("Before")
    suspendAndSetContinuation()
    continuation?.resume(Unit)

    println("After")
}

 

 

1. suspend fun main() 시작: main 코루틴이 실행됩니다.

2. println("Before") 실행: **"Before"**가 출력됩니다.

3. suspendAndSetContinuation() 호출:

  • 이 함수는 suspendCoroutine<Unit> { cont -> ... }을 호출합니다.
  • suspendCoroutine 블록이 실행되면, 코루틴은 일시 중단(Suspend) 상태가 되고, 현재 코루틴의 **재개 로직(continuation)**이 cont 변수로 전달됩니다.
  • 내부적으로 continuation = cont를 통해 이 재개 로직을 전역 변수 continuation에 저장합니다.
  • 코루틴은 여기서 멈춥니다. (suspendCoroutine의 목적은 코루틴을 중단하고 제어권을 외부로 넘기는 것입니다.)
  • main 코루틴 중단: suspendAndSetContinuation() 호출 후, main 코루틴은 다음 줄(continuation?.resume(Unit))을 실행하지 못하고 영구적으로 대기 상태에 들어갑니다.
  • 재개 시도 실패:
    • suspendCoroutine 블록이 실행된 후, 제어권은 main 함수로 돌아오지 않습니다. 제어권은 코루틴 디스패처로 넘어가고, 이 코루틴은 재개(resume)되기 전까지 다음 줄을 실행하지 않습니다.
    • 따라서 continuation?.resume(Unit) 코드는 절대로 실행되지 않습니다.

 

import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine


var continuation: Continuation<Unit>? = null

suspend fun suspendAndSetContinuation() {
    suspendCoroutine<Unit> { cont ->
        continuation = cont
    }
}

suspend fun main() = coroutineScope {
    println("Before")

    launch {
        delay(1000)
        continuation?.resume(Unit)
    }
    suspendAndSetContinuation()
    
    println("After")
}

 

launch 새로운 자식 코루틴이 재게 시켜줌.