Kotlin 공식문서
Cancelling coroutine execution
- 더이상 필요없어진 coroutine은 job에 대한 레퍼런스를 갖고 있다가 cancel해주자.
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L)
println("main: I'm tired of waiting!")
job.cancel()
job.join()
println("main: Now I can quit.")
job.cancelAndJoin()
으로 job.cancel()
과 job.join()
을 한번에 할 수 있다.
Cancellation is cooperative
- 모든 suspend function은 cancellable이고, CancellationException을 throw한다.
- 하지만, coroutine이
working in a computation
이고, cancellation 관련 체크를 하지 않으면 cancel되지 않는다.
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()
println("main: Now I can quit.")
Making computation code cancellable
- 위의 문제를 해결하기 위해 첫째로 loop 안에서 매 루프에 yield()를 호출하게 하는 방법이 있다.
yield()
했을 때 coroutine이 cancel되거나 complete되었다면 CancellationException을 throw한다.
- 루프 조건문에
isActive
를 확인하게 하는 방법이 있다.
closing resources with finally
- coroutine cancel은 CancellationException을 통해 진행된다.
- 아래처럼 try / finally로 cancel 완료 후의 작업을 정의한다. (물론 catch에도 걸린다.)
- 리소스 해지를 위한
use
function에서도 사용할 수 있다.(더 찾아볼 것)
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()
println("main: Now I can quit.")
Run non-cancellable block
- 특별한 경우 cancel된 coroutine에서 suspend function을 써야할 수도 있다.
- 이때는 아래와 같이
NonCancellable
coroutine context를 사용한다.
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
cancel()
하지 않고 withTimeout()
으로 TimeoutCancellationException
를 유발해 timeout시킬 수도 있다.
- try / catch를 이용해서 종료 액션을 처리할 수 있다.
- 또는
withTimeoutNull()
로 exception대신 null을 반환하게 하는 방식으로 구현할 수도 있다.
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
"Done"
}
println("Result is $result")
Asynchronous timeout and resources
- withTimeout을 사용했을 때
timeout 이벤트
와 block 내부의 코드
는 asynchronous하게 작동한다.
- 그러므로 따로 처리하지 않으면 block 내의 코드 실행을 보장할 수 없다.
- 아래와 같이 resource의 reference를 들고 finally에서
close()
를 보장할 수 있다.
runBlocking {
repeat(100_000) {
launch {
var resource: Resource? = null
try {
withTimeout(60) {
delay(50)
resource = Resource()
}
} finally {
resource?.close()
}
}
}
}
println(acquired)