코루틴을 사용한 예외처리는 꽤나 까다롭다.
두개의 코루틴(A, B)이 실행된다고 할 때 아래 상황을 고려할 수 있다.
각각 걸리는 시간
A: 1초
B: 2초
- A가 먼저 끝나고 B가 그 뒤에 예외가 발생함
- B가 실행 중에 A에서 예외가 발생함
- A, B 둘 다 예외가 발생함
- A, B 를 실행시키고 있는 부모 코루틴에서 예외가 발생함
- A, B 둘 중 하나에서 예외가 발생하였고 main스레드에 의해 runBlocking 안에서 실행되고 있을 때
결론부터 이야기하면 예외처리를 잘 하기 위해서는 국소적으로 try catch를 사용해주는 것이 권장된다.
아래는 CoroutineExceptionHandler에 나오는 docs 내용이다.
If you need to handle exception in a specific part of the code, it is recommended to use try/catch around the corresponding code inside your coroutine. This way you can prevent completion of the coroutine with the exception (exception is now caught), retry the operation, and/or take other arbitrary actions:
코루틴에는 CoroutineExceptionHandler 가 있어서 코루틴 안에서 발생한 Exception 을 처리하는 핸들러를 제공하고 있다.
아래 코드에서 기대하는 바는 RuntimeException 이 발생한 이후 handler에서 처리하였기 때문에 예외 없이 안전하게 끝나는 것으로 생각되지만, 실제로는 그렇지 못하다.
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler. ${Thread.currentThread().name} got $exception")
}
suspend fun parallelLaunchException_handler_not_working() = coroutineScope {
launch {
delay(100)
println("printed first")
}
launch {
delay(200)
throw RuntimeException("Boom")
}
delay(300)
}
fun main() = runBlocking(handler) {
parallelLaunchException_handler_working()
}
/**
메인 스레드는 RuntimeException을 처리하지 못하고 뱉어내게 된다.
printed first
Exception in thread "main" java.lang.RuntimeException: Boom
**/
CoroutineExceptionHandler 는 Top-Level 코루틴에서 UnCaughted Exception 을 처리하는데 사용된다고 한다.
사실 main 의 runBlocking 위로 어떤 코루틴이 더 있는지는 모르겠는데, 이 마저도 동작을 하지 않는다. (난감)
핸들러를 동작시키고자 한다면 아래처럼 GlobalScope 에서 코루틴을 실행시켜야 한다.
suspend fun parallelLaunchException_handler_working() = coroutineScope {
launch {
delay(100)
println("printed first")
}
GlobalScope.launch(handler) {
delay(200)
throw RuntimeException("Boom")
}
delay(300)
}
fun main() = runBlocking {
parallelLaunchException_handler_working()
}
/**
printed first
CoroutineExceptionHandler. DefaultDispatcher-worker-1 got java.lang.RuntimeException: Boom
**/
참고로 GlobalScope 는 싱글톤으로 만들어진 스코프인데, 이를 사용하여 실행시키면 root coroutine 으로 인식이 되고 최상위 코루틴에서까지 잡지 못한 예외를 처리하게 되는 것이다.
GlobalScope 는 이런저런 이유로 사용하지 말고, coroutineScope를 사용하여 코루틴의 범위를 직접 지정하는 것으로 권고하고 있다.
여튼 예외가 발생하는 곳으로부터 try, catch 를 사용해주면 된다.
1. 국소적으로 try, catch를 해주기
suspend fun parallelLaunchException_catch() = coroutineScope {
launch {
delay(100)
println("printed first")
}
launch {
try {
delay(200)
throw RuntimeException("Boom")
} catch (e: RuntimeException) {
println(e.message)
}
}
delay(300)
}
/**
printed first
Boom
**/
2. 전체를 한번 더 감싸기
suspend fun parallelLaunchException_catch() = coroutineScope {
launch {
delay(100)
println("printed first")
}
launch {
delay(200)
throw RuntimeException("Boom")
}
delay(300)
}
fun main() = runBlocking {
try {
parallelLaunchException_catch()
} catch (e: RuntimeException) {
println(e.message)
}
}
'코틀린' 카테고리의 다른 글
코루틴을 사용한 안전한 예외처리 - 3 (0) | 2022.01.14 |
---|---|
코루틴을 사용한 안전한 예외처리 - 2 (0) | 2022.01.14 |
코루틴을 사용한 동시성, 병렬처리 - 2 (0) | 2022.01.14 |
코루틴을 사용한 동시성, 병렬처리 - 1 (2) | 2022.01.14 |
QueryDsl 코틀린으로 안전하게 쓰기 (0) | 2022.01.11 |
댓글