Coroutine 예외처리
일반적인 예외처리
일반적으로 예외처리는 try ~ catch 구문을 이용해서 처리할 수 있습니다.
suspend fun main(): Unit = runBlocking { | |
launch { | |
try { | |
delay(1000) | |
throw Exception("ERROR") | |
println("AAA") | |
} catch (e: Exception) { | |
println("Exception : $e") | |
} | |
} | |
launch { | |
delay(2000) | |
println("BBB") | |
} | |
} |
또한, 코틀린에서는 runCatching ~ onSuccess ~ onFailure 구문으로 처리 가능합니다.
suspend fun main(): Unit = runBlocking { | |
launch { | |
kotlin.runCatching { | |
delay(1000) | |
throw Exception("ERROR") | |
"AAA" | |
}.onSuccess { | |
println(it) | |
}.onFailure { | |
println("Exception : $it") | |
} | |
} | |
launch { | |
delay(2000) | |
println("BBB") | |
} | |
} |
위처럼 코드를 작성하면 예외처리를 할 수 있지만, try ~ catch 혹은 runCatching ~ onSuccess ~ onFailure 구문이 반복되어 보일러 플레이트 코드를 작성하게 된다.
위와 같은 문제를 해결하는 방법은 코루틴의 SupervisorJob 이다.
SupervisorJob
SupervisorJob은 Coroutiine Child(코루틴 하위)에서 발생하는 Exception을 부모로 전파하지 않고 무시하도록 해준다.
suspend fun main(): Unit = runBlocking { | |
launch { // 1번 scope | |
launch { // 2번 scope | |
println("AAA") // A | |
} | |
delay(1000) | |
throw Exception("ERROR") // error | |
println("BBB") // B | |
} | |
launch { // 3번 scope | |
delay(2000) | |
println("CCC") // C | |
} | |
} | |
// 출력 | |
// AAA | |
// Exception in thread "main" java.lang.Exception: ERROR |
왜 A만 출력되고 B와 C는 출력되지 않았을까?
-> error가 발생하여 자식 scope(2번 scope)에서 부모 scope(1번 scope)로 예외가 전파되어 runBlocking scope 전체가 cancel 되었기 때문이다.
자식 scope에서 발생한 예외를 부모 scope로 전파하지 않도록 수정을 하면 다음과 같습니다.
suspend fun main(): Unit = runBlocking { | |
launch(SupervisorJob()) { // 1번 scope | |
launch { // 2번 scope | |
println("AAA") // A | |
} | |
delay(1000) | |
throw Exception("ERROR") // error | |
println("BBB") // B | |
} | |
launch { // 3번 scope | |
delay(2000) | |
println("CCC") // C | |
} | |
} | |
// 출력 | |
// AAA | |
// Exception in thread "main" java.lang.Exception: ERROR | |
// CCC |
위의 예시를 보면 1번 scope의 CoroutineContext 파라미터에 SupervisorJob()을 넣어주고 실행했더니 error가 발생했지만 runBlocking scope가 취소되지 않아 C가 출력됨을 확인할 수 있습니다.
CoroutineExceptionHandler
예외가 발생했을 때 SupervisorSuper()와 CoroutineExceptionHandler를 함께 이용하면 예외발생 처리가 간편해집니다.
val exceptionHandler = CoroutineExceptionHandler { _, exception -> | |
println("exception : $exception") | |
} | |
suspend fun main(): Unit = runBlocking { | |
launch(SupervisorJob() + exceptionHandler) { // 1번 scope | |
launch { // 2번 scope | |
println("AAA") // A | |
} | |
delay(1000) | |
throw Exception("ERROR") // error | |
println("BBB") // B | |
} | |
launch { // 3번 scope | |
delay(2000) | |
println("CCC") // C | |
} | |
} | |
// 출력 | |
// AAA | |
// exception : java.lang.Exception: ERROR | |
// CCC |
위 예시와 같이, CoroutineExceptionHandler scope에서 간편하게 예외를 처리할 수 있습니다.
val exceptionHandler = CoroutineExceptionHandler { _, exception -> | |
println("exception : $exception") | |
} | |
suspend fun main(): Unit = runBlocking(exceptionHandler) { | |
launch(SupervisorJob()) { // 1번 scope | |
launch { // 2번 scope | |
println("AAA") // A | |
} | |
delay(1000) | |
throw Exception("ERROR") // error | |
println("BBB") // B | |
} | |
launch(SupervisorJob()){ // 3번 scope | |
delay(2000) | |
throw Exception("ERROR2") // error2 | |
println("CCC") // C | |
} | |
launch() { // 3번 scope | |
delay(3000) | |
println("DDD") // D | |
} | |
} | |
// 출력 | |
// AAA | |
// exception : java.lang.Exception: ERROR | |
// exception : java.lang.Exception: ERROR2 | |
// DDD |
또한, 위와 같이 부모 scope에 exceptionHandler를 넣어주면 여러 자식 scope에서 발생한 예외를 받아서 처리할 수 있습니다.