출처 - https://tourspace.tistory.com/151?category=797357
Cancelling coroutine execution
coroutine도 취소해야할 경우가 있습니다. backgroud 작업을 진행중에 페이지가 넘어가서 더이상 결과가 필요하지 않다면 취소해야 겠지요?
이를 위해 launch function이 return하는 Job을 이용합니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("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.")
}
repeat으로 1000번 반복하지만 1300ms 이후에 취소되므로 아래와 같은 결과가 나타납니다.
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
job을 cancel후 join 시켰지만 cancelAndJoin 명령으로 한번에 처리 할 수도 있습니다.
Cancellation is cooperative
kotlinx.coroutines에서 제공하는 모든 suspending functions은 취소를 지원하며, coroutine이 취소되면, CancellationException을 발생시킵니다.
하지만 아래와 같이 연속된 계산작업인 경우에는 취소시키지 않습니다.
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // computation loop, just wastes CPU
// yield() //여기에 추가하면 정상적으로 취소된다
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("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문이 전부(다섯번) 돌면서 로그를 직고 나서야 main()함수가 종료됩니다.
이런 computation code를 취소시키는 방법은 두가지 입니다.
- yield function을 이용하여 추기적으로 취소를 체크하는 suspend function을 invoke 시킨다.
- 명시적으로 취소 상태를 체크한다. (isActive 이용)
fun main() = runBlocking {
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("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.")
}
위 예제처럼 isActivie를 이용하면 loop도 취소 가능합니다.
isActive는 확장된 property로, CoroutineScope object를 통하여 coroutine code 내부에서 사용할수 있습니다.
Closing resources with finally
coroutine을 취소했으나, resource를 닫는다거나, 마지막으로 해야할 작없이 필요할때는 try - finally 구문을 이용합니다.
취소시 onCancellationException이 발생하기 때문에 여기서 처리해도 상관 없습니다.)
또한 코틀린에서 resource 해지를 위해 사용하는 use function도 coroutine 취소시 정상적으로 동작합니다.
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
} finally {
println("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.")
}
위 코드의 결과는 아래와 같습니다.
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
main: Now I can quit.
join 및 cancelAndJoin은 finallization action이 완료될 때까지 기다려 줍니다.
Run non-cancellable block
위 예제에서 만약 finally 내부에 다시 suspending function을 사용한다면 cancellationException이 또 발생합니다.
보통 finally에서 파일을 닫거나, 통신 channel을 해제하는 작업은 suspending function을 포함하지 않기 때문에 상관없습니다만, 부득이 하게 finally에서 suspending function을 사용하야 한다면 withContext와 NonCancellable context로 감싸줘야 합니다.
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
} finally {
withContext(NonCancellable) {
println("I'm running finally")
delay(1000L)
println("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
일일히 Job으로 cancel하지 않고, 특정 시간이후에 취소하도록 하려면 withTimeout을 사용하면 됩니다.
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
실제로 Exception을 발생시키면서 main()이 끝납니다.
안드로이드에서는 try-catch 처리를 하지 않으면 앱이 중지되면서 끝납니다.
위 예제와 같이 cancel() 또는 cancelAndJoin()으로 coroutine을 취소하는 경우에는 try-catch를 하지 않아도 앱은 정상 종료됩니다.
따라서 android에서 withTimeout 쓸때는 꼭 try-catch로 묶어서 사용해야 앱이 중지 되는걸 방지할 수 있습니다.
fun withTimetoutTest() {
runBlocking {
try {
withTimeout(10000L) {
repeat(1000) { i ->
Log.e(TAG, "I'm sleeping $i...")
delay(500L)
}
}
} catch (te: TimeoutCancellationException) {
Log.e(TAG, "Timetout!!!")
}
}
}
TimeoutCancellationException은 CancellationException을 상속받아 만들어진 객체 입니다.
따라서 timeout 처리가 필요하다면 try {...} catch (e: TimeoutCancellationException) {...} 구문을 사용하면 되며 withTimeoutOrNull을 이용하면 exception throw 대신 null을 return 합니다.
fun main() = runBlocking {
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
withTimeout과 withTimeoutOrNull은 코드블럭 내부의{...} 마지막값을 return 합니다.
'개발 > 코틀린(kotlin)' 카테고리의 다른 글
[Kotlin] 코틀린 - 코루틴#1 기본! (0) | 2020.01.30 |
---|---|
[Kotlin] 코틀린 constructor vs init block (0) | 2020.01.30 |
[Kotlin] 코틀린 Generic #1 (0) | 2020.01.29 |
[Kotlin] 코틀린 High order function (0) | 2020.01.29 |
[Kotlin] 코틀린 연산자 오버로딩 #2 컬렉션, in, rangeTo, iterator, destructuring, Property delegation, by (0) | 2020.01.29 |
댓글