티스토리 뷰
이번에는 코루틴에서 취소와 타임아웃 그리고 어떻게 취소가 가능한지 구조에 대해 알아보고자 한다.
코루틴 실행중에 어떻게 취소하지?
애플리케이션의 실행 중에, 백그라운드 코루틴에 대한 세분화된 제어가 필요할 수 있다.
예를 들어 MSA환경에서 다른 서버에 필요한 데이터를 동시 요청하다가,
의도치 않은 실패에 의해 , 더 이상 필요하지 않아져 작업을 취소할 수 있다.
이를 위해 launch 함수는 실행중인 코루틴을 취소하는데 사용할 수 있는 job을 반환한다.
job의 cancel과 join 함수를 통해 코루틴을 정지할 수 있다.
val job = launch {
repeat(1000) { i ->
println("job: 잠깐 잠 좀 잘게요 $i ...")
delay(500L)
}
}
delay(1300L)
println("main: 기다리다 지쳤어요 작업 취소할게요~!")
job.cancel()
job.join()
println("main: 멈췄어요! ")
위 코드를 보면, launch에 의해 실행된 코루틴에서
repeat 함수에 의해 1000번이나 0.5초가 걸리는 작업을 반복한다.
그러나, 아래 delay에 의해 1.3초까지는 기다려주나..
이후에 대해서는 해당 코루틴에 대해 cancel 함수를 호출하여 정지를 요구한다.
그리고 cancel 함수의 동작이 완료되기를 해당 코루틴을 기다리는 join 함수를 호출 후,
멈췄음을 출력한다.
여기서 의문이 생길 수 있다.
왜 cancel 함수 후에 join을 사용했을까?
(물론 cancelAndJoin함수를 통해서 한 번에 호출도 가능하나, 동작은 같다.)
이유에 대해서 간단히 설명하자면, cancel은 코루틴 취소 요청을 보내지만 바로 작업이 취소되기 보다는, 관련 작업이 있기 때문에 이에 대한 처리로 시간이 걸릴 수 있다.
이전에 구조화된 코루틴에 대해 얘기한 바가 있다. ((1)에서 했다.)
만약 특정 부모코루틴이 정지되었는데 자식 코루틴은 여전히 살아있다면? 고아 코루틴이 된다. 이를 방지하고자, 코루틴은 구조화된 동시성 원칙을 따른다.
이러한 논리로 보았을 떄, 코루틴의 정지 요청이 오면 현재 코루틴과 연관된 코루틴에 대해 추가적인 취소 작업이 체이닝된다.
그렇기 때문에 이러한 추가 작업에 의한 취소 작업의 완료를 join으로 기다리는 것이다.
코루틴의 취소는 협조적으로 이뤄져야한다.
코루틴의 취소는 협조적(cooperative)으로 이뤄져야하는데,
즉, 코루틴 코드가 스스로 취소를 허용해야만 취소될 수 있어야한다.
이게 무슨 소리일까?
kotlinx.coroutines의 모든 일시 중단 함수(suspending functions) 는 취소 가능하다.
이들은 코루틴의 취소 상태를 확인하고, 취소되면 CancellationException을 발생시킨다.
하지만 만약에!!!
코루틴이 CPU Bound 작업을 수행 중이며 취소 여부를 확인하지 않는다면, 취소되지 않는다.
예시로 아래 코드는 취소가 즉시 반영되지 않는다.
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // 단순히 CPU를 소비하는 연산 루프
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: 자고 있어요.. ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L)
println("main:기다리다 지쳤어!")
job.cancelAndJoin()
println("main: 끝이야 우린")
여기서 출력으로는 자고 있다는 출력문이 5번 반복한 후에야 종료된다.
왜? CPU바운드 작업만 있으면 왜 바로 확인을 못 하는가?
기본적으로, job.cancel()을 호출한다고 해서 강제로 코루틴의 실행을 중단시키지 않는다.
대신 코루틴 내부에서 취소 요청이 왔는지를 확인할 기회를 준다면?
확인후 취소가 동작한다.
이전 코드의 while(i<5)문은 순수한 연산으로 단순히 반복한다.
( suspend함수 없이 취소 신호를 감지할 기회가 없다...)
즉, 이 루프 안에서는 코루틴이 취소되었는지 확인하는 코드가 없기 때문에, 취소를 감지할 수 없다는 의미다.
그렇기 때문에 아래 코드와 같이 확인가능하게끔 한다면 멈출 수 있다.
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // 반복을 멈출수 있는지를 확인
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L)
println("main:기다리다 지쳤어!")
job.cancelAndJoin()
println("main: 끝이야 우린")
그렇다면, CancellationException을 잡고 다시 던지지 않는 경우에도 문제가 발생할 수 있지 않을까?
val job = launch(Dispatchers.Default) {
repeat(5) { i ->
try {
println("job: I'm sleeping $i ...")
delay(500)
} catch (e: Exception) {
// 예외를 로그만 남김 (다시 던지지 않음)
println(e)
}
}
}
delay(1300L)
println("main:기다리다 지쳤어!")
job.cancelAndJoin()
println("main: 끝이야 우린")
그렇다... 취소된 뒤에도 계속 출력이 된다.
즉, Exception을 잡아버리면 CancellationException도 함께 잡혀버려서 취소가 무시될 수 있다.
finally로 리소스를 닫아야한다면?
취소 가능한 일시 중단 함수는 취소 시 CancellationException을 발생시키며 , 이는 일반적인 방식으로 처리할 수 있다.예를 들어, try {...} finally {...}표현식과 Kotlin의 use 함수는 코루틴이 취소될 때 정상적으로 종료 작업을 실행한다.
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
println("job: 리소스 닫을게요~")
}
}
delay(1300L)
println("main:기다리다 지쳤어!")
job.cancelAndJoin()
println("main: 끝이야 우린")
timeout?
코루틴 실행을 취소하는 가장 대표적인 이유는 실행 시간이 일부 시간 초과를 초과했기 때문일 것이다. 해당 작업 에 대한 참조를 수동으로 추적하고 지연 후 추적된 작업을 취소하기 위해 별도의 코루틴을 시작할 수 있지만, 이를 수행하는 사용 가능한 withTimeout 함수가 있다.
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
음..이제는 어떻게 코루틴을 정지할 수 있는지 알 것 같다.
cancel 함수를 사용하거나 timeout을 일으키거나...
CancelException을 일으켜 해당 코루틴이 감지하고 정지할 수 있도록 하는것이다.
그런데 궁금하다.
어떻게 코루틴을 정지시키는가?
정확히는 어떻게 원리로 코루틴이 되어 있기에 코루틴의 상태를 감지하고 정지하는걸까?
다음 장에 좀 더 살펴보자.
참고) https://kotlinlang.org/docs/cancellation-and-timeouts.html
Cancellation and timeouts | Kotlin
kotlinlang.org
'Kotlin' 카테고리의 다른 글
코틀린 코루틴? 원문을 보고 말지 (5) + Dispatcher (0) | 2025.03.16 |
---|---|
코틀린 코루틴? 원문을 보고 말지 (4) + 비동기 (0) | 2025.03.15 |
코틀린 코루틴? 원문을 보고 말지 (3) + 코루틴 구조 (with ctrl) (0) | 2025.03.15 |
코틀린 코루틴? 원문을 보고 말지 (1) + runBlocking, launch (0) | 2025.03.10 |
코틀린 코루틴? 원문을 보고 말지 (preview) (1) | 2025.03.10 |