일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- Android Compose
- 안드로이드 무한 스크롤
- Hilt 에러
- hilt
- Android Compose Navigation
- Sticky Header RecyclerView
- Coroutine
- BOJ
- 백준 2615
- viewmodel
- power menu
- 코루틴 공식문서
- 코루틴
- Thread vs Coroutine
- 백준
- runCatching
- Kotlin Serialization
- Unsupported metadata version. Check that your Kotlin version is >= 1.0: java.lang.IllegalStateException
- 스레드 vs 코루틴
- 안드로이드 커스텀 뷰
- android orbit
- 코루틴 공식 문서
- 코틀린 에러 핸들링
- RecyclerView Sticky Header
- 힐트
- Android Custom View
- 안드로이드 컴포즈
- android compose orbit
- AAC ViewModel
- power menu 라이브러리
- Today
- Total
Beeeam
코루틴 공식 문서 읽기 (Coroutine Cancellation and timeouts) 본문
https://kotlinlang.org/docs/cancellation-and-timeouts.html
Cancellation and timeouts | Kotlin
kotlinlang.org
코루틴에서 실행되는 모든 중단 함수들은 취소 요청에 응답을 할 수 있어야 한다. 그러면 코드 실행중 취소 요청이 있는지를 반복적으로 확인해야 하는데 코루틴은 이러한 취소 요청에 대응할 수 있도록 구현이 되어 있다.
취소 요청에 대응하여 코루틴이 취소 되면 CancellationException을 발생 시키면서 종료를 한다.
Cancelling coroutine execution (코루틴 실행 취소)
애플리케이션이 장시간 동안 실행되고 있으면 백그라운드의 코루틴들을 관리해야 할 필요가 생긴다. 예를 들어 사용자가 실행된 코루틴 페이지를 닫기 하면 이 코루틴 실행 결과는 필요가 없어지고, 작동이 취소될 수 있다. launch 함수는 Job이라는 객체를 반환하는데 얘는 진행 중인 코루틴을 취소하는데 사용될 수 있다.
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.
위의 결과를 보면 코루틴이 실행되어 "job: I'm sleeping $i ..." 이라는 출력 결과가 나오다가 job.cancel() 함수가 호출되자 코루틴이 종료되는 것을 확인할 수 있다.
그리고 취소와 join을 동시에 할 수 있는 확장 함수인 job.cancelAndJoin()이 존재한다. 얘를 사용하면 위의 예시처럼 job.cancel() , job.join()을 따로 사용하지 않아도 된다.
Cancellation is cooperative
코루틴 코드들은 취소에 협조적이여야 한다. kotlinx.coroutines 안의 모든 suspending 함수들은 취소 될 수 있다. 얘네들은 취소 될 때 코루틴의 취소를 확인하고, 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.
job.cancelAndJoin()을 만났는데도 job: I'm sleeping … 이 나오는 것을 볼 수 있다. 이 작업은 코루틴 안의 while 반복문이 다 끝날 때 까지 실행이 되고, 종료가 되었다.
Making computation code cancellable(위의 문제 해결 방법)
계산 중인 코드를 취소하는 방법은 2가지가 있다.
- 취소를 확인하는 suspending 함수를 주기적으로 호출한다. (yield 함수가 이에 적합하다. )
- 명시적으로 취소 상황을 확인한다. (isActive 사용)
fun main() = runBlocking {
val startTime = 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
**yield()**
if (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.")
}
위의 코드는 첫 번째 방법이다. 이전 예시의 코드에서 달라진 점은 while 반복문을 시작할 때 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.")
위의 코드는 두 번째 방법이다. while 반복문에 isActive을 넣어서 취소 요청이 들어오지 않는 동안에만 반복문을 실행하도록 하였다.
위의 두 개의 코드를 실행하면 둘 다 동일하게 밑의 결과가 나온다.
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
취소할 수 있는 suspending 함수들은 취소할 때 CancellationException(취소 예외)이 발생하는데 이 CancellationException을 이용하여 예외 처리를 할 수 있다.
예를 들면 try { … } 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.
코루틴 내의 try { } 안의 동작이 진행되다가 job.cancelAndJoin()을 만나면서 job: I'm sleeping ... 이 출력되지 않고, job: I'm running finally 이 출력 되는 것을 볼 수 있다.
Run non-cancellable block
바로 이전에 다뤘던 예제에서 finally 함수를 사용하면 코루틴이 취소 되면서 CancellationException 이 발생하였다. 파일을 닫거나, 작업을 취소하거나 어떠한 종류의 통신 채널을 닫는 정상적인 closing 동작은 보통 non-blocking 되지 않고, 어떠한 suspending 함수도 호출하지 않아서 문제가 되지 않는다. 하지만 밑의 예제와 같이 withContext(NonCancellable) { … } (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")
}
}
}
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
job: And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.
job.cancelAndJoin()을 만나서 job: I'm sleeping ... 의 실행이 끝났지만 finally 구문 안의 withContext(NonCancellable) 함수가 실행되는 것을 볼 수 있다.
Timeout
withTimeout(시간) 함수를 사용하면 실행 시간이 일정 시간을 초과하게 되면 실행이 취소가 된다.
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
…
withTimeout(시간) 함수 안에 인자로 1300L을 할당하였는데 I'm sleeping $i ... 가 3번 출력되고 4번째 실행이 될 때 실행 시간이 1300L을 초과하기 때문에 시간 초과 Exception이 발생한다.
Timeout Exception은 예외라서 예외를 발생하면서 강제로 함수를 종료한다. 근데 만약 특별한 시간 초과 예외에 대해서 처리해야 할 코드가 있으면 어떻게 해야 할까?
withTimeoutOrNull() 함수를 사용하면 된다. 이 함수는 시간 초과가 발생하면 예외를 발생하지 않고 null 값을 반환한다.
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
withTimeoutOrNull() 함수를 사용하자 예외가 발생하지 않는 것을 볼 수 있다. 그리고 변수 result 안에는 withTimeoutOrNull() 의 결과로 null 값이 할당 된 것도 확인 할 수 있다.
'Kotlin' 카테고리의 다른 글
코루틴 공식 문서 읽기 (Asynchronous Flow 2) (1) | 2023.04.13 |
---|---|
코루틴 공식 문서 읽기 (Asynchronous Flow 1) (0) | 2023.04.13 |
코루틴 공식 문서 읽기 (Coroutine context and dispatchers) (0) | 2023.03.28 |
코루틴 공식 문서 읽기 (Composing suspending functions) (0) | 2023.03.13 |
코루틴 공식 문서 읽기 (Coroutines basics) (1) | 2023.03.03 |