예외 처리
예외 처리는 코루틴의 작동 원리 중 중요한 기능이다. 잡히지 않은 예외가 발생하면 프로그램이 종료되는 것처럼 코루틴도 잡히지 않는 예외가 발생했을 때 종료된다. 예외는 자식에서 부모로 전파되며, 부모가 최소 되면 자식도 취소되기 때문에 쌍방으로 전파된다. 예외 전파가 정지되지 않으면 계통 구조상 모든 코루틴이 취소되게 된다.
코루틴 종료 멈추기
코루틴이 종료되기 전에 예외를 잡는 건 도움이 되지만.. 조금이라도 늦으면 이미 늦어버린 것이다..
코루틴 간의 상호작용은 Job을 통해서 일어나기 때문에, 코루틴 빌더 내부에서 새로운 코루틴 빌더를 try-catch 문을 통해 래핑하는 건 전혀 도움이 되지 않는다. 그러니 예외 처리를 잘 이해해 보자
SupervisorJob
코루틴 종료를 멈추는 가장 중요한 방법은 `SupervisorJob`을 사용하는 것이다. `SupervisorJob`을 사용하면 자식에서 발생한 모든 예외를 무시할 수 있다.
suspend fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob())
scope.launch {
delay(1000)
throw Error("오류남")
}
scope.launch {
delay(2000)
println("정상 출력")
}
delay(3000)
}
???: 아 그럼 부모 코루틴의 인자로 사용하면 되는 건가?
-> 흔한 실수 중 하나이다.
`SupervisorJob`은 단 하나의 자식만 가지기 때문에 아래 코드처럼 사용하게 되면 예외를 처리하는데 아무런 도움이 되지 않는다.
자식 코루틴 하나가 있고 부모 코루틴이 없는 Job은 일반 Job과 동일하게 작동한다.
그러니 아래 코드처럼 사용하면 안 된다!
suspend fun main() = runBlocking {
launch(SupervisorJob()) {
launch {
delay(1000)
throw Error("오류남")
}
launch {
delay(2000)
println("이게 보이면 그건 환상")
}
}
delay(3000)
}
supervisorScope
예외 전파를 막는 또 다른 방법은 코루틴 빌더를 `supervisorScope`로 래핑하는 것이다. 다른 코루틴에서 발생한 예외를 무시하고 부모와의 연결을 유지한다는 점에서 아주 편리하다.
suspend fun main() = runBlocking {
supervisorScope {
launch {
delay(1000)
throw Error("오류남")
}
launch {
delay(2000)
println("정상 출력")
}
}
delay(1000)
println("완료")
}
CancellationException은 부모까지 전파되지 않아
예외가 `CancellationException`의 서브 클래스라면 부모로 전파되지 않는다. 현재 코루틴을 '최소'시킬 뿐이다.
아래 코드에서는 두 개의 코루틴이 1과 4의 빌더로 시작된다. 3에서 `CancellationException`의 서브타입인 `CustomCancelException` 예외를 던진다. 예외는 1에서 시작된 `launch`에서 잡히게 된다. 1에서 시작된 코루틴은 자기 자신을 취소하고 2에서 정의된 빌더로 만들어진 자식 코루틴 또한 취소시킨다. 4에서 시작된 `launch`는 영향을 받지 않고 2초 후 '정상 출력'을 출력한다.
class CustomCancelException: CancellationException()
suspend fun main(): Unit = coroutineScope {
launch { // 1
launch { // 2
delay(2000)
println("이게 보이면 그것은 환상")
}
throw CustomCancelException() // 3
}
launch { // 4
delay(2000)
println("정상 출력")
}
}
코루틴 예외 핸들러
예외를 다룰 때 예외를 처리하는 기본 행동을 정의하는 것이 유용할 때가 있다. 이런 경우 `CoroutineExceptionHandler`컨텍스트를 사용하면 편리하다. 예외 전파를 중단시키지는 않지만 예외가 발생했을 때 해야 할 것들을 정의하는 데 사용할 수 있다.
안드로이드에서는 사용자에게 대화창이나 에러 메시지를 보여줌으로써 어떤 문제가 발생했는지 알리는 역할을 할 수 있겠다.
suspend fun main(): Unit = runBlocking {
val handler = CoroutineExceptionHandler { coroutineContext, throwable ->
println("잡았다 요놈 $throwable ")
}
val scope = CoroutineScope(SupervisorJob() + handler)
scope.launch {
delay(1000)
throw Error("오류남")
}
scope.launch {
delay(2000)
println("정상 출력")
}
delay(3000)
}
참고자료
마르친 모스카와(2023), 코틀린 코루틴 Kotlin Coroutines : Deep Dive - 안드로이드 및 백엔드 개발자를 위한 비동기 프로그래밍, 인사이트
https://kotlinlang.org/docs/exception-handling.html
'Kotlin Coroutines' 카테고리의 다른 글
[Kotlin Coroutines] 디스패처 (1) | 2024.05.27 |
---|---|
[Kotlin Coroutines] 코루틴 스코프 함수 (0) | 2024.05.20 |
[Kotlin Coroutines] 코루틴 취소 (0) | 2024.05.17 |
[Kotlin Coroutines] Job 과 자식 코루틴 기다리기 (0) | 2024.05.16 |
[Kotlin Coroutines] 코루틴 빌더에 대해 알아보자 (0) | 2024.05.10 |