Cancellation and Timeouts

CmplxN·2021년 1월 16일
0

Kotlin 공식문서

Cancelling coroutine execution

  • 더이상 필요없어진 coroutine은 job에 대한 레퍼런스를 갖고 있다가 cancel해주자.
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.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) { // 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.")

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) // 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.")

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" // will get cancelled before it produces this result
}
println("Result is $result")

Asynchronous timeout and resources

  • withTimeout을 사용했을 때 timeout 이벤트block 내부의 코드는 asynchronous하게 작동한다.
  • 그러므로 따로 처리하지 않으면 block 내의 코드 실행을 보장할 수 없다.
  • 아래와 같이 resource의 reference를 들고 finally에서 close()를 보장할 수 있다.
runBlocking {
    repeat(100_000) { // Launch 100K 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
            }
        }
    }
}
// Outside of runBlocking all coroutines have completed
println(acquired) // Print the number of resources still acquired
profile
Android Developer

0개의 댓글