[Coroutine] suspendCoroutine과 suspendCancellableCoroutine

지난 callbackFlow에 이어서 callback를 coroutine 형태로 받을 수 있는

suspendCoroutine과 suspendCancellableCoroutine에 대해 알아보려고 합니다.


1. suspendCoroutine

public suspend inline fun <T> suspendCoroutine(
	crossinline block: (Continuation<T>) -> Unit): T {
}

먼저 suspendCoroutine에 대해 알아보도록 하겠습니다. 내부 코드와 함께 적힌 설명을 살펴보면, 

block 람다 함수는 Continuation 객체를 인자로 받으며, 비동기 작업이 완료되면 결과를 이 Continutaion 객체를 통해 반환합니다. 

Continuation 객체는 현재 코루틴 상태를 나타내며, Continuation.resume과  Continuation.resumeWithException을 통해서 값을 반환해 주거나 throw exception을 해줄 수 있습니다.

 

 

2. suspendCancellableCoroutine

inline suspend fun <T> suspendCancellableCoroutine(
	crossinline block: (CancellableContinuation<T>) -> Unit): T

suspendCancellableCoroutine은 suspendCoroutine과 같이 코루틴을 일시 중단하지만 블록에 CancellableContinuation 객체를 제공합니다. CancellableContinuation 객체는 resume 및 resumeWithException 메서드를 통해서 결과를 반환하거나 예외를 던질 수 있고, cancel를 통해 코루틴을 취소할 수 있습니다. 즉, suspendCancellableCoroutine를 사용하면 코루틴이 취소될 때 비동기 작업을 중단하고 정리할 수 있으며, 파일 시스템을 사용하거나 resource의 해제, 소켓 close 등의 처리를 위해 사용하는 것이 안정적입니다.

 

 

그럼 suspendCoroutine와 suspendCancellableCoroutine은 동작에서 어떤 차이가 있을까요? 간단한 예제와 함께 알아보도록 하겠습니다.

 

fun main(): Unit = runBlocking {

    val suspendCancellableCoroutine = launch {
        val a = suspendCancelableCoroutineTest()
        println(a)
    }
    val suspendCoroutine = launch {
        val b = suspendCoroutineTest()
        println(b)
    }

    launch {
        delay(500)
        suspendCancellableCoroutine.cancel()
        suspendCoroutine.cancel()
    }
}

suspend fun suspendCoroutineTest(): String = suspendCoroutine { continuation ->
    CoroutineScope(Dispatchers.IO).launch {
        delay(1000)
        continuation.resume("suspendCoroutineTest")
    }
}


suspend fun suspendCancelableCoroutineTest(): String = suspendCancellableCoroutine { continuation ->
    CoroutineScope(Dispatchers.IO).launch {
        delay(1000)
        continuation.resume("suspendCancelableCoroutineTest") {
            println("cancel!!")
        }
    }
}

간단하게 String 값을 반환하는 suspendCoroutineTest()와 suspendCancelableCoroutineTest()를 만들고 delay를 1초 주었습니다. 두 함수를 실행한 후 500 밀리 세컨드 후에 취소하는 함수를 실행해 보면 결과는 어떻게 나올까요?

//실행결과
suspendCoroutineTest
cancel!!

위 결과와 같이 suspendCoroutineTest는 취소되지 않고 결과값을 반환하며 suspendCancelableCoroutineTest는 취소되며 resume의 cancel 콜백이 실행되어 "cancel!!"을 출력하게 됩니다.

 

 

그렇다면 지난 번에 알아본 callbackFlow와 suspendCoroutine은 어떤 차이점이 있을까요? 

 

3. suspendCoroutine vs callbackFlow

suspendCoroutine은 반환값이 단일 객체이기 때문에 값을 한 번만 받아서 사용하는 일회성 callback에서 주로 사용됩니다. 이와 달리 callbackFlow는 Flow스트림을 반환하기 때문에 지속적으로 관찰이 필요하거나 값이 전달되는 callback에서 사용하는 것이 좋습니다.

callbackFlow의 예로는 Socket 연결 후 전달되는 지속적인 message에 대한 callback, android의 TextWatcher 등이 있습니다.