Suspend (중단)

  • 스레드는 저장이 불가능하고 멈추는 것만 가능하지만, 코루틴은 중단하고 재개가 가능하다

  • 코루틴은 중단 되었을때, Continuation 객체를 반환한다.

  • suspend 함수(중단 함수)코루틴을 중단할 수 있는 함수이다.

    • suspend 함수는 코루틴 또는 다른 suspend 함수에서 호출 되어야한다.
    • suspend 함수는 중단할 수 있는 중단점이 필요하다.
1
2
3
4
5
6
suspend fun main() {
    println("Before")
    println("After")
}
// Before
// After
1
2
3
4
5
6
suspend fun main() {
    println("Before")
    suspendCoroutine<Unit> { }
    println("After")
}
// Before
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//3
suspend fun main() {
    println("Before")
    suspendCoroutine<Unit> { continuation ->
        println("Before too")
    } // 람다 표현식이 suspend 되기 전에 실행된다.
    println("After")
}
// Before
// Before too

Resume (재개)

  • 중단된 코루틴은 Continuation 객체를 이용해 재개 가능하다.
  • 다른 스레드에서 코루틴을 재개할 수 있다.
  • 코루틴이 있으면 중단함과 동시에 “데이터를 받고 나면, 받은 데이터를 resume 함수를 통해 보내줘” 라고 Continuation 에 전달
  • 코루틴에서는 값으로 재개 한다.(Continuation의 제네릭 타입 인자)
1
2
3
4
5
6
7
8
9
suspend fun main() {
    println("Before")
    suspendCoroutine<Unit> { continuation ->
        continuation.resume(Unit)
    }
    println("After")
}
// Before
// After
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
suspend fun main() {
    println("Before")

    suspendCoroutine<Unit> { continuation ->
        thread {
            println("Suspended")
            Thread.sleep(1000)
            continuation.resume(Unit)
            println("Resumed")
        }
    }
    println("After")
}
// Before
// Suspended
// (1 second delay)
// After
// Resumed
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
fun requestUser(callback: (User) -> Unit) {
    thread {
        Thread.sleep(1000)
        callback.invoke(User("Test"))
    }
}

suspend fun main() {
    println("Before")
    val user = suspendCoroutine<User> { cont ->
        requestUser { user -> cont.resume(user) }
    }
    println(user)
    println("After")
}
// Before
// (1 second delay)
// User(name=Test)
// After
  • suspend 와 resume 을 통해, 스레드는 다른 일을 할 수 있고, 값 또는 데이터가 도착하면 스레드는 코루틴이 중단된 지점에서 재개 하게 됩니다.

  • 이때 resume 함수와 Continuation 객체를 통해 값을 얻을 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private val executor =
    Executors.newSingleThreadScheduledExecutor {
        Thread(it, "scheduler").apply { isDaemon = true }
    }

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

suspend fun main() {
    println("Before")
    delay(1000)
    println("After")
}
  • 스레드를 생성하는 것 보다, ‘알람 시계’를 설정하는계 더 좋다
    • ScheduledExecutorService, 정해진 시간이 지나면 continuation.resume(Unit)을 호출하도록 알람 설정
  • excutordelay 함수를 사용하는 모든 코루틴의 전용 스레드

값으로 재개 하기

1
val ret: Unit = suspendCoroutine<Unit> { cont: Continuation<Unit> -> cont.resume(Unit) }
  • Unit은 함수의 리턴 타입이며, Continuation의 제네릭 타입 인자이다.
  • resume을 통해 반환되는 값은 반드시 지정된 타입과 같은 타입이여야 한다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
suspend fun main() {
    val i: Int = suspendCancellableCoroutine<Int> { cont ->
        cont.resume(42)
    }
    println(i) // 42

    val str: String = suspendCancellableCoroutine<String> { cont ->
        cont.resume("Some text")
    }
    println(str) // Some text

    val b: Boolean = suspendCancellableCoroutine<Boolean> { cont ->
        cont.resume(true)
    }
    println(b) // true
}
  • 코루틴이 있으면 중단함과 동시에 “데이터를 받고 나면, 받은 데이터를 resume 함수를 통해 보내줘” 라고 Continuation 에 전달
    • 그러면 스레드는 다른 일을 할 수 있다.
    • 데이터가 도착하면 스레드는 코루틴이 중단된 지점에서 재개 한다.

예외로 재개하기

  • 서비스가 종료되거나 에러로 응답이 오는 경우, 코루틴이 중단된 곳에서 예외를 발생시키고, 이 예외로 재개하는 방법

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    suspend fun main() {
    	try {
            suspendCorotine<Unit> { cont ->
                cont.resumeWithException( MyException() )
            }
        } catch (e: MyException) {
            println("Caught!")
        }
    }
    

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

  • 중단 함수는 코루틴 자체가 아니다.
  • 중단 함수함수가 아닌, 코루틴중단할 수 있는 함수 이다.
  • 다른 스레드나 다른 코루틴으로 재개하지 않으면 프로그램은 실행된 상태로 유지된다

Continuation.kt (code 중 일부)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Interface representing a continuation after a suspension point that returns a value of type `T`.
 */
public interface Continuation<in T> {
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext

    /**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */
    public fun resumeWith(result: Result<T>)
}

/**
 * Resumes the execution of the corresponding coroutine passing [value] as the return value of the last suspension point.
 */
public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

/**
 * Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
 * last suspension point.
 */
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))


public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
        val safe = SafeContinuation(c.intercepted())
        block(safe)
        safe.getOrThrow()
    }
}

Reference