이번 글에서는 Job의 Exception을 Handling하는 방법을 살펴볼 것이다.
Exception을 Handling하는 방법은
- invokeOnCompletion을 이용한 방법과
- CoroutineExceptionHandler 을 이용하는 방법
이 있다. 먼저 invokeOnCompletion을 사용하는 방법을 살펴보자.
invokeOnCompletion을 이용한 Exception Handling
앞서 7번 글에서 다웠던 invokeOnCompletion을 코루틴 내부에서 애러가 발생했을 때도 사용할 수 있다.
아래와 같이 invokeOnCompletion을 사용해 Exception을 Handling할 수 있다.
fun main() = runBlocking<Unit> {
val job = launch(Dispatchers.IO) {
throw IllegalArgumentException()
}
job.invokeOnCompletion { cause: Throwable? ->
println(cause)
}
job.start()
}
// java.lang.IllegalArgumentException 출력
하지만, 이 방법은 완료가 되었을 때 호출되는 람다식에서 핸들링 하는 것이다. 애러가 발생해 완료되든 Job이 모두 수행되어 완료되든 해당 람다식이 호출되는 것이므로, 애러를 핸들링 하는 부분을 분리하기 위해 조금 더 General한 방법이 필요하다.
그 방법이 바로 CoroutineExceptionHandler을 이용한 방법이다.
CoroutineExceptionHandler 이용하기
CoroutineExceptionHandler은 코루틴(Job) 내부에서 오류가 발생했을 때 애러를 처리할 수 있는 CoroutineContext이다. CoroutineContext에 대해서는 안다루었으므로, 그냥 간단히 생각해서 애러가 발생했을 때 실행되는 람다식이라고 생각하면 편하다.
아래와 같은 코드를 수행해보자.
fun main() = runBlocking<Unit> {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler : $exception")
}
val job = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
throw IllegalArgumentException()
}.join()
}
exceptionHandler는 CoroutineExceptionHandler을 구현하며 exception이 왔을 때 받은 exception을 출력해준다.
따라서 위의 코드를 수행하면 아래와 같은 결과가 나온다.
CoroutineExceptionHandler : java.lang.IllegalArgumentException
Process finished with exit code 0
CoroutineExceptionHandler는 여러 Job에 붙일 수도 있다. 예를 들어 아래와 같은 코드를 수행해보자.
fun main() = runBlocking<Unit> {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler : $exception")
}
CoroutineScope(Dispatchers.IO + exceptionHandler + SupervisorJob()).apply {
val job1 = launch {
throw IllegalArgumentException()
}.join()
val job2 = launch {
throw InterruptedException()
}.join()
}
}
그러면 출력은 다음과 같다. IllegalArgumentException과 InterruptedException이 발생한 것을 확인할 수 있다.
CoroutineExceptionHandler : java.lang.IllegalArgumentException
CoroutineExceptionHandler : java.lang.InterruptedException
Process finished with exit code 0
CoroutineExceptionHandler을 이용하여 애러에 맞게 처리하기
위의 코드를 조금 응용하면, when문을 이용하여 exception에 대해 타입 검사를 해서 다음과 같이 애러의 유형별로 처리하도록 만들 수도 있다.
fun main() = runBlocking<Unit> {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler : $exception")
when(exception){
is IllegalArgumentException -> println("More Arguement Needed To Process Job")
is InterruptedException -> println("Job Interrupted")
}
}
CoroutineScope(Dispatchers.IO + exceptionHandler + SupervisorJob()).apply {
val job1 = launch {
throw IllegalArgumentException()
}.join()
val job2 = launch {
throw InterruptedException()
}.join()
}
}
위의 코드를 수행하면 다음의 결과가 나온다.
CoroutineExceptionHandler : java.lang.IllegalArgumentException
More Arguement Needed To Process Job
CoroutineExceptionHandler : java.lang.InterruptedException
Job Interrupted
Process finished with exit code 0
정리
Coroutine에서 공통 예외 처리기가 필요할 때는 CoroutineExceptionHandler을 이용하도록 하자.