개요

코루틴에서 실행되는 모든 중단 함수(suspending function)들은 취소 요청에 응답 가능하도록 구현되어야 합니다. 다시말해 중단 함수는 실행 중 취소 가능한 구간마다 취소 요청이 있었는지 확인하고 요청이 있었다면 실행을 즉시 취소하도록 구현되어야 합니다. kotlinx.coroutines 라이브러리의 모든 중단함수는 이러한 취소 요청에 대응 하도록 구현되어 있습니다.

앞서 이야기 한 것처럼 취소를 지원하는 중단 함수들은 실행하는 동안 취소가 가능한 지점마다 현재 코루틴이 취소 되었는지 확인하며, 만약 취소 되었다면 CancellationException 을 발생시키며 종료합니다.

출처: Medium Article

위 글처럼 코루틴의 중요한 특징 중 하나는 취소가 가능하다는 점이다.

이번 글에서는 코루틴의 취소에 대해 알아본다.

코루틴 취소

코루틴 컨텍스트가 갖고 있는 element 중 Job 인터페이스는 코루틴을 취소하는 cancel 메서드를 제공한다.

cancel 메소드를 호출하면 호출당한 코루틴은 첫 중단점에서 Job을 끝낸다.

이때 Job에 자식이 존재하면, 자식들도 취소된다(부모는 영향 x). 또한 취소된 Job은 새로운 코루틴의 부모로 사용될 수 없다.

suspend fun main(): Unit = coroutineScope {
    val job = launch {
        repeat(1_000) { i ->
            delay(200)
            println("job: I'm sleeping $i ...")
        }
    }

    delay(1100L)
//    job.cancel()
//    job.join() // cancel 이 호출된 뒤 다음 작업을 진행하기 전에 취소 과정이 완료되는 걸 기다리기 위해 join 사용
    job.cancelAndJoin() // cancel + join 을 한번에 호출
    println("main: I'm tired of waiting!")
    println("main: Now I can quit.")
}

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

취소의 작동

  1. Job이 취소되면 상태는 Cancelling으로 변한다.
  2. 상태가 바뀌면 첫 중단점에서 CancellationException 예외를 던진다.
  3. try-catch로 예외를 잡을 수 있으므로 finally 블록에서 파일 닫기 등 자원 정리 수행이 가능하다.
suspend fun main(): Unit = coroutineScope {
    val job = Job()
    launch(job) { // 새로운 잡이 부모로부터 상속받은 잡을 대체
        try {
            repeat(1_000) { i ->
                delay(Random.nextLong(2000))
                println("job: I'm sleeping $i ...")
            }
        } catch (e: CancellationException) {
            println(e)
            throw e // 취소된 코루틴이 단지 멈추는 것이 아니라 내부적으로 예외를 사용해 취소됨
        } finally {
            println("Will Always execute")
        }
    }

    delay(1100L)
    job.cancelAndJoin()
    println("main: Now I can quit.")
    delay(1000)

		// 출력
		job: I'm sleeping 0 ...
		kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@4618452e
		Will Always execute
		main: Now I can quit.

		// or
		kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@1a55dda5
		Will Always execute
		main: Now I can quit.
}

취소 중 코루틴 호출

코루틴을 취소하면 CancellationException이 던져지므로, finally 블록에서 추가 연산 수행이 가능하다고 했다. 하지만 Job이 이미 Cancelling 상태이므로 중단을 허용하지 않고, 다른 코루틴을 시작하려 하면 이를 무시한다.