Hanbit the Developer
Kotlin Documentation | Cancellation and timeouts 본문
Category: Official libraries - Coroutines
문서 링크: https://kotlinlang.org/docs/cancellation-and-timeouts.html
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's 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!
main: Now I can quit.
Cancellation is cooperative
코루틴 취소는 협력적이고 코루틴 코드는 취소가 가능하도록 협조적이어야 한다. kotlinx.coroutines의 모든 정지 함수는 취소할 수 있다. 해당 함수들은 코루틴 취소를 체크하여 취소될 경우 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.
또한 아래와 같이 예외 처리를 할 경우 문제가 발생한다. 이때 취소 체크는 delay(kotlinx.coroutines) 함수가 하여 예외가 던져진 것이다.
val job = launch(Dispatchers.Default) {
repeat(5) { i ->
try {
// print a message twice a second
println("job: I'm sleeping $i ...")
delay(500)
} catch (e: Exception) {
// log the exception
println(e)
}
}
}
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!
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@7b68429b
job: I'm sleeping 3 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@7b68429b
job: I'm sleeping 4 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@7b68429b
main: Now I can quit.
Making computation code cancellable
- yield() 함수를 두는 방법
- 명시적으로 취소 상태를 검사하는 방법:
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
// 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!
main: Now I can quit.
Closing resources with finally
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
println("job: I'm running finally")
}
}
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 running finally
main: Now I can quit.
Run non-cancellable block
코루틴이 취소된 후 정지 함수를 호출해야 하는 상황이라면 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")
}
}
}
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.")
Timeout
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
at (Coroutine boundary. (:-1)
at FileKt$main$1$1.invokeSuspend (File.kt:-1)
at FileKt$main$1.invokeSuspend (File.kt:-1)
Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
at kotlinx.coroutines.TimeoutKt .TimeoutCancellationException(Timeout.kt:184)
at kotlinx.coroutines.TimeoutCoroutine .run(Timeout.kt:154)
at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask .run(EventLoop.common.kt:508)
타임아웃 시 TimeoutCancellationException(CancellationException의 서브 클래스)이 발생한다.
예외를 처리하기 위해 try {...} catch (e: TimeoutCancellationException)로 묶거나 withTimeoutOrNull()을 호출할 수 있다.
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")
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null
Asynchronous timeout and resources
열었으면 닫아야 하는 리소스가 있는데, 관련 작업을 코루틴이 언제 취소될지 모르는 withTimeout 내에서 진행한다면 언제나 리소스가 방출된다는 것을 보장할 수 없다. 예시는 다음과 같다:
repeat(10_000) { // Launch 10K coroutines
launch {
val resource = withTimeout(60) { // Timeout of 60 ms
delay(50) // Delay for 50 ms
Resource() // Acquire a resource and return it from withTimeout block
}
resource.close() // Release the resource
}
}
위 코드에서 close() 함수가 항상 호출되지는 않는다.(Resource 객체를 생성하는 데 10ms를 초과하는 시간이 걸린 경우)
이를 해결하기 위해 다음과 같은 코드를 작성할 수 있다:
repeat(10_000) { // Launch 10K coroutines
launch {
var resource: Resource? = null // Not acquired yet
try {
withTimeout(60) { // Timeout of 60 ms
delay(50) // Delay for 50 ms
resource = Resource() // Store a resource to the variable if acquired
}
// We can do something else with the resource here
} finally {
resource?.close() // Release the resource if it was acquired
}
}
}
'Kotlin' 카테고리의 다른 글
Kotlin Documentation | Coroutine context and dispatchers (0) | 2023.05.24 |
---|---|
Kotlin Documentation | Composing suspending functions (0) | 2023.05.24 |
Kotlin Documentation | Coroutines and channels - tutorial (0) | 2023.05.24 |
Kotlin Documentation | Coroutines basics (0) | 2023.05.24 |
Kotlin Documentation | Opt-in requirements (0) | 2023.05.24 |