Coroutine 실행 취소
- launch를 통해 반환되는 job의 cancel로 코루틴의 실행을 취소할 수 있다.
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 약간의 시간 동안 delay 한다.
println("main: I'm tired of waiting!")
job.cancel() // Job을 cancel한다.
job.join() // Job의 실행이 완료될 때까지 기다린다.
println("main: Now I can quit.")
}
- main 함수가 job.cancel을 호출하면, Job이 취소되었기 때문에 다른 코루틴의 출력을 확인할 수 없다. Job의 확장 함수로 cancel과 join 호출을 결합한 cancelAndJoin도 있다.
- join은 코루틴이 종료될 때까지 기다리는 suspend 함수이다. 결과를 반환하지 않는다.
Coroutines 취소는 협력적이다
- Coroutine의 취소는 협력적이다. Coroutine 코드는 취소 가능하도록 협력해야 한다.
- kotlinx.coroutines 패키지의 모든 일시 중단 함수들은 취소 가능하다.
- 그들은 Coroutine이 취소되었는지 확인하고 취소되었을 경우 CancellationException을 발생시킨다.
- 하지만 코루틴이 게산 작업 진행 중이고, 중간에 취소를 확인해주지 않으면 계속 진행한다.
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // 계산 루프, CPU를 낭비한다
// 1초에 두 번 메세지를 출력한다.
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // 약간의 시간 동안 delay 한다
println("main: I'm tired of waiting!")
job.cancelAndJoin() // Job을 취소하고 실행이 완료될 때까지 기다린다.
println("main: Now I can quit.")
}
- 또한 취소 시 발생하는 CancellationException을 catch 했다가 다시 throw 하지 않는 경우에도 취소되지 않는다.
fun main() = runBlocking {
val job = launch(Dispatchers.Default) {
repeat(5) { i ->
try {
// 1초에 두 번 메세지를 출력한다.
println("job: I'm sleeping $i ...")
delay(500)
} catch (e: Exception) {
// 예외를 로깅한다.
println(e)
}
}
}
delay(1300L) // 약간의 시간 동안 delay 한다.
println("main: I'm tired of waiting!")
job.cancelAndJoin() // Job을 취소하고 완료될 때까지 기다린다.
println("main: Now I can quit.")
}
Coroutine의 Computation 코드를 취소 가능하게 만들기
- computation code를 취소 가능하게 만드는 두 가지 접근 방식이 있다.
- 첫번째는 주기적으로 일시 중단 함수를 실행시켜서 취소되었는지 확인하도록 하는 것이다. (yield 함수 사용)
- 두번째는 명시적으로 취소 상태를 확인하는 것이다.
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // 활성 상태를 매번 검사
// 1초에 두 번 메세지를 출력한다.
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L) // 약간의 시간 동안 delay 한다
println("main: I'm tired of waiting!")
job.cancelAndJoin() // Job을 취소하고 실행이 완료될 때까지 기다린다.
println("main: Now I can quit.")
}
finally 사용해 리소스 닫기
- 취소 가능한 일시 중단 함수는 취소 시에 CancellationException을 throw하며, 이는 일반적인 방식으로 처리할 수 있다. 예를 들어 try { ... }와finally {... } 구문이나, Kotlin의 use 함수는 Coroutine이 취소될 때 정상적으로 종료 작업을 수행한다.
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
println("job: I'm running finally")
}
}
delay(1300L) // 잠시 기다리기
println("main: I'm tired of waiting!")
job.cancelAndJoin() // Job을 취소하고 완료될 때까지 기다리기
println("main: Now I can quit.")
}
실행 취소가 불가능한 블록 실행하기
- 드물게 취소된 Coroutine에 대해 일시 중단을 해야 하는 경우 다음과 같이 withContext함수에 NonCancellable Context를 전달하여 사용하는 withContext(NonCancellable) { ... }를 사용할 수 있다.
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
withContext(NonCancellable) {
println("job: I'm running finally")
delay(1000L)
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
Timeout
- 일정 시간이 지나면 TimeoutCancellationException을 발생시켜 코루틴을 취소시킬 수 있는 방법이 있다.
fun main() = runBlocking {
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
}
- 결과 값을 null을 반환하고 싶을 때에는 withTimeoutOrNull을 사용할 수도 있다.
fun main() = runBlocking {
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
"Done" // will get cancelled before it produces this result
}
println("Result is $result")
}