일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 세그먼트 트리
- 위상정렬
- 완전탐색
- 후니의 쉽게 쓴 시스코 네트워킹
- 백준
- Network
- CS
- mst
- 플로이드-와샬
- 동적계획법
- 그리디
- 에라토스테네스의 체
- Kotlin
- 시뮬레이션
- Effective Java
- 이분탐색
- 수학
- BFS
- 구현
- 백트래킹
- 유니온 파인드
- dfs
- swea
- 프로그래머스
- 투 포인터
- 알고리즘
- JUnit 5
- java
- 문자열
- 스택
반갑습니다!
[Kotlin] Cancellation and Timeouts 본문
Cancelling coroutine execution
이번엔 코루틴을 멈추는 방법을 알아보자. 이전의 예제들을 보면서 launch
를 통해 Job
객체를 반환받을 수 있음을 알았다. 코루틴을 멈추는 방법은 반환받은 Job 객체에 구현된 cancel()
함수를 실행하는 것이다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 1300ms동안 기다리기
println("main: I'm tired of waiting!")
job.cancel() //job 취소
job.join() // job이 종료될 때까지 대기
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
위의 예제에서는 설명하지 않았지만 사실, 코루틴을 취소하기 위해서는 코루틴 안에 suspend
함수가 호출되어야 한다. 그렇기 때문에 suspend
함수의 호출 없이 단순 연산만을 수행하는 코루틴은 중지시킬 수 없다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) {
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L)
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.
*/
cancelAndJoin()
함수는 그저 cancel()
과 join()
을 순차적으로 실행하는 함수이다. 위 예제에서 코루틴은 멈춰지지않고 모든 작업을 완료한 뒤 종료되었다. 코루틴 안에 suspend
함수가 호출되지 않았기 때문이다. 이번엔 suspend
함수인 delay()
를 추가해보겠다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) {
delay(1L) // suspend 함수인 delay 추가
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L)
println("main: I'm tired of waiting!")
job.cancelAndJoin()
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.
*/
suspend
함수를 추가함으로서 정상적으로 중단되는 것을 볼 수 있다. 그렇지만 코틀린을 중단하기 위해서 delay()
를 넣는 것은 적절하지 않아보인다. 이를 위해서 존재하는 함수가 yield()
이다. 위의 예제에서 delay()
를 yield()
로 변경하면 정상적으로 동작함을 알 수 있을 것이다. 내부적으로 원리를 조금 설명하자면 suspend
함수는 실행하기 전에 cancel이 되었는지를 확인하고 cancel 되었다면 JobCancellationException 을 발생시켜 코루틴을 종료시킨다.
Making computation code cancellable
그렇다면 suspend
함수가 없다면 코루틴은 중단할 수 없는 것인가? 답은 '아니오' 이다. 그렇다면 어떤 방식으로 할 수 있을지 다음 예제를 보면서 알아보자.
import kotlinx.coroutines.*
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // 코루틴 내부에 있는 isActive의 상태 확인
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1300L)
println("main: I'm tired of waiting!")
job.cancelAndJoin()
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.
*/
코루틴 내부에는 ìsActive
라는 Boolean 형 변수가 있다. 이 변수는 cancel을 하면 false로 변하기 때문에 이 값을 사용해서 코루틴을 제어할 수 있다.
import kotlinx.coroutines.*
fun main() = runBlocking {
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: I'm tired of waiting!")
job.cancelAndJoin()
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.
*/
isActive
가 처음 방식과 다른 점은 exception을 발생시키지 않는다는 점이다.
Closing resources with finally
suspend
를 사용하면 코루틴을 멈출 때 예외를 발생시키기 때문에 이를 이용해서 리소스의 해제를 쉽게 할 수 있다.
import kotlinx.coroutines.*
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) // 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.
*/
위 예제를 보면 코틀린을 멈추자 예외가 발생해 finally
블록이 실행되었음을 알 수 있다. 이를 이용해 코틀린 중단 시 finally
블록 안에서 리소스를 반납해주면 된다.
Run non-cancellable block
간혹가다 중지된 코루틴에서 지연을 해야할 때가 있을 수 있다. 이러한 경우 withContext()
함수와 NonCancellable
context를 사용하면 된다.
import kotlinx.coroutines.*
fun main() = runBlocking {
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)
println("main: I'm tired of waiting!")
job.cancelAndJoin()
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.
*/
Timeout
이번엔 코루틴 자체적으로 Timeout을 설정해서 종료하는 방식이다.
import kotlinx.coroutines.*
fun main() = runBlocking {
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 ...
...
*/
이 예제를 실행해보면 TimeoutCancellationException이 발생하면서 종료되었다. 예외처리 코드를 별도로 추가해도 되지만 예외처리 없이 좀 더 간단하게 코드를 작성하면 다음과 같이 작성할 수 있다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
"Done" // 코루틴이 완전히 실행되면 "Done"을 반환
}
println("Result is $result")
}
/* 실행결과
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null
*/
withTimeoutOrNull()
함수를 사용하면 timeout이 발생한 경우 예외를 발생시키는 것이 아니라 null값을 리턴한다.
'Kotlin' 카테고리의 다른 글
[Kotlin] Principle of Coroutine (0) | 2020.07.05 |
---|---|
[Kotlin] Composing Suspending Functions (0) | 2020.07.05 |
[Kotlin] Coroutine Basics (0) | 2020.07.04 |
[Kotlin] Coroutine Guide (0) | 2020.07.04 |
[Kotlin] 반복문 (0) | 2020.06.15 |