- 코틀린 코루틴에서 아주 중요한 기능 중 하나는 바로 취소(cancellation)임.
- 중단 함수를 사용하는 몇몇 클래스와 라이브러리는 취소를 반드시 지원하고 있음.
- 코틀린 코루틴의 취소 방식은 아주 간단하고 편리하며 안전함.
기본적인 취소
- Job 인터페이스는 취소하게 하는
cancel
메서드를 가지고 있음.
cancel
메서드를 호출하면 다음과 같은 효과를 가져올 수 있음.
- 호출한 코루틴은 첫 번째 중단점에서 잡을 끝냄.
- 잡이 자식을 가지고 있다면, 그들 또한 취소되지만 부모는 영향을 받지 않음.
- 잡이 취소되면, 취소된 잡은 새로운 코루틴의 부모로 사용될 수 없음. 취소된 잡은 Cancelling 상태가 되었다가 Cancelled 상태로 바뀜.
suspend fun main(): Unit = coroutineScope {
val job = launch {
repeat(1_000) { i ->
delay(200)
println("Printing $i")
}
}
delay(1100)
job.cancel()
job.join()
println("Cancelled success")
}
실행결과
Printing 0
Printing 1
Printing 2
Printing 3
Printing 4
Cancelled success
cancel
이 호출된 뒤 다음 작업을 진행하기 전에 취소 과정이 완료되는 걸 기다리기 위해서 join
을 사용하는 것이 일반적임.
join
을 사용하지 않으면 race condition이 될 수 있음.
join
을 추가하여 코루틴이 취소를 마칠 때까지 증단되므로, race condition이 발생하지 않음.
cancelAndJoin
확장함수를 제공함.
- 이는
cancel
과 join
을 함께 호출하는 방법임.
- 팩토리 함수로 생성된 Job()도 같은 방법으로 취소됨.
- 이는 잡에 따린 수많은 코루틴을 한번에 취소할 때 자주 사용됨.
- 팩토리 함수로 생성된 Job은 부모 Job으로 사용되기 때문.
취소는 어떻게 작동하는가?
- 잡이 취소되면 Cancelling 상태로 바뀜.
- 상태가 바뀐 뒤 첫 번째 중단점에서
CancelationException
예외를 던짐.
- 예외는
try-catch
구문을 사용하여 잡을 수도 있지만, 다시 던지는 것이 좋음.
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
try {
repeat(1_000) { i ->
delay(200)
println("Printing $i")
}
} catch (e: CancellationException) {
println(e)
throw e
}
}
delay(1100)
job.cancelAndJoin()
println("Cancelled success")
}
실행결과
Printing 0
Printing 1
Printing 2
Printing 3
Printing 4
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@53908b12
Cancelled success
- 취소된 코루틴이 단지 멈추는 것이 아니라 내부적으로 예외를 사용해 취소되는 걸 명심해야함.
취소 중 코루틴을 한 번 더 호출하기
CancelationException
을 잡고 코루틴은 모든 자원을 정리할 필요가 있는 한 계속해서 실행될 수 있음.
- 하지만 정리 과정중 중단은 허용하지 않음
- Job은 이미 Cancelling 상태가 되었기 때문에 중단되거나 다른 코루틴을 시작하는 건 절대 불가능함.
- 다른 코루틴을 실행하면 그냥 무시해버림.
- 중단하려고 하면
CancelationException
을 던짐.
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
try {
repeat(1_000) { i ->
delay(200)
println("Printing $i")
}
} finally {
println("Finally")
launch { // 무시됨
println("Will not be printed")
}
delay(1000) // 예외 발생
println("Will not printed")
}
}
delay(1100)
job.cancelAndJoin()
println("Cancelled success")
}
실행결과
Printing 0
Printing 1
Printing 2
Printing 3
Printing 4
Finally
Cancelled success