관리 메뉴

나만을 위한 블로그

[Kotlin] 코루틴의 취소와 suspendCancellableCoroutine 본문

개인 공부/Kotlin

[Kotlin] 코루틴의 취소와 suspendCancellableCoroutine

참깨빵위에참깨빵 2023. 8. 6. 00:32
728x90
반응형

suspendCancellableCoroutine(이하 scc)은 코루틴 라이브러리에 속한 API 중 하나로, 취소 가능한 비동기 연산을 가능하게 하고 어떤 코루틴이 취소되면 연산도 취소되게 하는 기능을 지원한다.

물론 코루틴 자체적으로도 cancel()을 지원하고 있다. 그러나 코루틴의 cancel()을 잘못 사용하게 되면 cancel() 호출 후에도 코루틴이 작동을 멈추지 않고 작동할 수 있다. 이것은 코틀린 공식문서에서도 설명하는 내용이다.

 

https://kotlinlang.org/docs/cancellation-and-timeouts.html#cancellation-is-cooperative

 

Cancellation and timeouts | Kotlin

 

kotlinlang.org

(중략)...kotlinx.coroutines의 모든 suspend function은 취소 가능하다. 코루틴의 취소가 확인되고 취소되면 CancellationException을 발생시킨다. 그러나 코루틴이 계산 작업 중이고 취소를 확인하지 않으면 취소할 수 없다
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
    var nextPrintTime = startTime
    var i = 0
    while (i < 5) { // computation loop, just wastes CPU
        // print a message twice a second
        if (System.currentTimeMillis() >= nextPrintTime) {
            println("job: I'm sleeping ${i++} ...")
            nextPrintTime += 500L
        }
    }
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")

// job: I'm sleeping 0 ...
// job: I'm sleeping 1 ...
// job: I'm sleeping 2 ...
// main: I'm tired of waiting!
// job: I'm sleeping 3 ...
// job: I'm sleeping 4 ...
// main: Now I can quit.

 

이에 대응하는 방법은 while 조건의 "i < 5"를 없애고 대신 isActive 키워드를 넣어서, 해당 코루틴이 살아있는지를 따져서 false라면 곧바로 코루틴이 종료되게 하는 것이다. isActive는 CoroutineScope 객체를 통해 코루틴 안에서 사용할 수 있는 확장 프로퍼티다.

그러나 이 방법 외에도 다른 방법이 있다. 그게 이 포스팅의 제목인 SCC다.

 

공식문서에서 설명하는 SCC는 아래와 같다.

 

https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html

 

suspendCancellableCoroutine

suspendCancellableCoroutine Suspends the coroutine like suspendCoroutine, but providing a CancellableContinuation to the block. This function throws a CancellationException if the Job of the coroutine is cancelled or completed while it is suspended. A typi

kotlinlang.org

suspendCoroutine과 같이 코루틴을 일시 중단하지만 블록에 CancellableException을 제공한다. 코루틴의 작업이 일시 중단된 동안 취소하거나 완료되면 이 함수는 CancellableException을 발생시킨다. 이 함수의 일반적인 용도는 싱글 샷 콜백 API의 결과를 기다리는 동안 코루틴을 일시 중단하고 호출자에게 결과를 반환하는 것이다. 멀티샷 콜백 API에 대해선 callbackFlow를 참고하라

 

코루틴의 cancel()은 코루틴이 suspend 상태일 때 코루틴의 작업을 중지시키고 CancellableException을 발생시킨다. 그러나 위에서 말한 대로 비동기 API 호출이나 시간이 오래 걸리는 CPU 작업의 경우 코루틴을 취소해도 해당 작업은 계속 진행될 수 있다.

suspendCancellableCoroutine의 역할은 이 작업을 취소하면서 추가적으로 세세한 작업을 진행할 수 있게 도와준다. 네트워크 호출이나 대량의 데이터를 처리할 경우 불필요한 작업을 중단하고 리소스를 정리하는 작업이 필요할 수 있다. 그 때 유용하게 사용할 수 있다.,

아래는 간단한 suspendCancellableCoroutine의 예시다.

 

import kotlinx.coroutines.*
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

fun main() = runBlocking {
    val job = launch {
        runCatching {
            println("코루틴 시작됨")
            val result = longRunningTask()
            println("result : $result")
        }.onFailure {
            println("코루틴 취소됨 : $it")
        }
    }

    delay(1000L)
    job.cancel()
    println("메인 쓰레드")
}

suspend fun longRunningTask(): Int = suspendCancellableCoroutine { continuation ->
    // 코루틴이 취소될 경우 invokeOnCancellation으로 정리 작업 실행
    continuation.invokeOnCancellation {
        println("코루틴이 중간에 취소됨. 정리 작업 시작")
    }

    // 코루틴을 사용하면서 블로킹 작업이 필요하면 별도 쓰레드에서 실행되게 하는 게 좋다
    Thread {
        runCatching {
            Thread.sleep(3000L)
            println("3초 경과")
            // 비동기 작업이 끝나면 resume()으로 코루틴 재개 or resumeWithException()으로 예외 발생시킴
            continuation.resume(10)
        }.onFailure {
            continuation.resumeWithException(it)
        }
    }.start()
}

// 코루틴 시작됨
// 코루틴이 중간에 취소됨. 정리 작업 시작
// 메인 쓰레드
// 코루틴 취소됨 : kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@73035e27
// 3초 경과

 

runCatching이 생소하다면 try-catch로 바꿔서 작성해도 된다. 둘이 같은 역할을 하지만 runCatching을 쓰는 편이 좀 더 코드가 읽기 좋아서 개인적으로 사용한 것 뿐이다.

longRunningTask()의 블로킹 작업은 별도의 쓰레드에서 실행되고 메인 쓰레드를 중지시키지 않는다. 3초 후에 비동기 작업이 끝나면 resume()을 써서 코루틴을 재개한다. 그리고 비동기 처리 중 에러가 발생해서 onFailure가 호출되면 resumeWithException() 함수를 통해 해당 예외를 밖으로 던진다. 마지막으로 코루틴이 취소된다면 invokeOnCancellation()을 써서 정리 작업을 수행한다. 이 경우 Thread {}의 로직이 실행되지 않는 건 아니다. 정리 작업 수행 후 Thread {}가 작동한다.

 

참고한 사이트)

 

https://devroach.tistory.com/162

 

Coroutine Series-1 ) 코루틴을 왜 사용해야 하는가?

왜 코루틴을 사용해야 할까? Java 에는 이미 멀티스레딩을 잘 지원하는 JavaFX 나 Reactor 와 같은 표준화된 라이브러리가 존재한다. 그럼에도 우리가 코루틴을 사용해야 하는 이유는 무엇일까? 코루

devroach.tistory.com

 

https://tourspace.tistory.com/442

 

[Coroutine] suspendCancellableCoroutine - Callback을 coroutine으로 변경

coroutine는 callback을 사용하지 않고 비동기 처리를 가능해주는 장점을 가지고 있습니다. 따라서 비동기 처리를 코드의 순서대로 실행시키며 가독성을 높이고, 보다 심플한 코드를 작성할 수 있도

tourspace.tistory.com

 

반응형
Comments