-
[Coroutine] 코루틴 학습 - 8 (Exception handling)Java/Kotlin 2022. 5. 5. 06:56반응형
Exception handling
- 코루틴은 익셉션이 발생하면 해당 코루틴을 cancel 시키고, 해당 익셉션을 부모 코루틴으로 전파한다.
- 익셉션을 전파받은 부모 코루틴은 자신과 자신의 자식 코루틴들을 cancel 시키고, 또 다시 자신의 부모 코루틴으로 익셉션을 전파한다.
- 위 과정을 반복해서 최상위 코루틴까지 예외가 전파된다.
fun main(): Unit = runBlocking { launch { launch { delay(1000) throw Error("Some error") } launch { delay(2000) println("출력되지 않는다.") } launch { delay(300) println("출력된다.") } } launch { delay(2000) println("출력되지 않는다.") } }
- 위 코드에서 예외가 발생하는 코루틴을 try-catch로 래핑하여 감싸는 방법은 Job을 통해서 서로 커뮤니케이션이 일어나는 코루틴의 특성 때문에 cancel 이 전파되는 것을 막을 수 없다.
fun main(): Unit = runBlocking { launch { try { launch { delay(1000) throw Error("Some error") } } catch (e: Throwable) { println("출력되지 않는다.") } launch { delay(2000) println("출력되지 않는다.") } } }
SupervisorJob
SupervisorJob은 자식 코루틴에게서 발생하는 cancel 을 무시하도록 하는 Job이다.
fun main(): Unit = runBlocking { launch { val supervisorJob = SupervisorJob() launch(supervisorJob) { delay(1000) throw Error("Some error") } launch(supervisorJob) { delay(2000) println("Inner Coroutine") } supervisorJob.join() } launch { delay(3000) println("Outer Coroutine") } } // Exception in thread "main" java.lang.Error: Some error // Innter Coroutine // Outer Coroutine
일반적으로 부모 코루틴을 argument로 넘기는 경우에 발생하는 실수는 아래의 코드와 같다.
fun main(): Unit = runBlocking { // Bad practice launch(SupervisorJob()) { launch { delay(1000) throw Error("Some error") } launch { delay(2000) println("Inner Coroutine") } } delay(3000) }
위 코드는 SupervisorJob이 오직 하나의 자식 코루틴만 있기 때문에 예외 처리에 도움이 되지 않는다. 아래와 같이 여러 coroutine builder의 컨텍스트로 작업을 수행하는 것이 더 좋다. 이는 자식 코루틴들이 각자 취소될 수 있지만, 서로 영향을 주어 취소시키진 않는다.
fun main(): Unit = runBlocking { val job = SupervisorJob() launch(job) { delay(1000) throw Error("Some error") } launch(job) { delay(2000) println("출력된다") } job.join() } // (1 sec) // Exception.. // (1 sec) // 출력된다
supervisorScope
예외가 전파되는 것을 멈추는 또다른 방법은 coroutine builder를 supervisorScope로 래핑하는 것이다. 이는 부모 코루틴과 연결을 유지하기 때문에 편리한 방법이다. 그리고 코루틴으로부터의 예외는 묵음처리 된다.
fun main(): Unit = runBlocking { supervisorScope { launch { delay(1000) throw Error("Some error") } launch { delay(2000) println("Inner 코루틴") } } delay(1000) println("Done") } // Exception // Inner 코루틴 // (1 sec) // Done
supervisorScope는 일시중단 함수이며, 일시중단 함수 본문을 래핑하는데 사용할 수 있다. 이는 일반적으로 서로 독립적인 여러 작업들을 시작할 때 많이 사용한다.
Await
예외가 발생하는 케이스에서 코루틴 빌더 async는 launch와 와 다른 코루틴 빌더들과 마찬가지로 그와 관련된 부모 코루틴을 중단시킨다. 하지만 supervisorScope를 이용하면 마찬가지로 cancel이 전파되는 것을 막을 수 있다.
아래의 코드에서는 supervisorScope 내에서 async 가 바깥으로 예외를 던지는 상황인데, 다른 async는 중단되지 않고 실행이 완료된다.
suspend fun main() = supervisorScope { val str1 = async<String> { delay(1000) throw MyException() } val str2 = async { delay(2000) "Text2" } try { println(str1.await()) } catch (e: MyException) { println(e) } println(str2.await()) } // MyException // Text2
CancellationException
발생한 예외가 CancellationException의 서브클래스인 경우에는 부모 클래스로 예외를 전파하지 않고, 현재 코루틴 내에서만 예외가 발생한다.
suspend fun main(): Unit = coroutineScope { launch { launch { delay(2000) println("출력되지 않는다.") } throw CustomNonPropagatingException } launch { delay(3000) println("출력된다.") } }
Coroutine exception handler
- CoroutineExceptionHandler를 이용하면 익셉션을 핸들링할 때 기본적으로 수행해야 하는 로직들을 처리하기 편리하다.
- 핸들러는 예외가 전파되는 것을 막지는 않지만, 예외가 발생했을 때 수행해야 할 로직들을 설정할 수 있다. (로깅 등..)
- 아래 코드는 CoroutineExceptionHandler와 예외의 전파를 막을 수 있는 SupervisorJob()을 이용한 예시 코드이다.
- SupervisorJob으로 인해 예외가 전파되지 않기 때문에 두 번째 자식 코루틴이 중지되지 않고 계속 실행된다.
fun main(): Unit = runBlocking { val handler = CoroutineExceptionHandler { ctx, exception -> println("exception - $exception") } val scope = CoroutineScope(SupervisorJob() + handler) scope.launch { delay(1000) throw Error("Some error") } scope.launch { delay(2000) println("출력된다.") } scope.coroutineContext.job.children.forEach { it.join() } }
참고자료
https://www.amazon.com/Kotlin-Coroutines-Deep-Marcin-Moskala/dp/8396395837
반응형'Java > Kotlin' 카테고리의 다른 글
[Coroutine] 코루틴 학습 - 10 (Dispatchers) (0) 2022.05.10 [Coroutine] 코루틴 학습 - 9 (Coroutine scope function) (0) 2022.05.07 [Coroutine] 코루틴 학습 - 7 (Cancellation) (0) 2022.05.03 [Coroutine] 코루틴 학습 - 6 (Job and children awaiting) (0) 2022.05.01 [Coroutine] 코루틴 학습 - 5 (Coroutine context) (0) 2022.04.28