이 글은 아래 링크의 내용을 기반으로 하여 설명합니다.
https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
또한 예제에서 로그 print시 println과 안드로이드의 Log.e()를 혼용합니다.
Exception propagation
Coroutine builder들을 Exception handling 측면에서 두가지 타입으로 나뉩니다.
- Exception을 외부로 전파(propagation) 시킴: launch, actor
- Exception을 노출(exposing)시킴: async, produce
fun main() = runBlocking {
val job = GlobalScope.launch {
println("Throwing exception from launch")
throw IndexOutOfBoundsException() // Will be printed to the console by Thread.defaultUncaughtExceptionHandler
}
job.join()
println("Joined failed job")
val deferred = GlobalScope.async {
println("Throwing exception from async")
throw ArithmeticException() // Nothing is printed, relying on user to call await
}
try {
deferred.await()
println("Unreached")
} catch (e: ArithmeticException) {
println("Caught ArithmeticException")
}
}
Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException
Joined failed job
Throwing exception from async
Caught ArithmeticException
lauch의경우 exception이 발생하면 바로 예외가 발생합니다.
하지만 async의 경우 코드가 수행되어 exception이 있더라도 실제로 exception이 발생되는 부분은 await()를 만날때 입니다.
전파와 노출의 차이가 구분 되시나요?
CoroutineExceptionHandler
사실 둘중 뭘해도 콘솔에 exception은 발생합니다.
이를 방지하기 위해 CoroutineExceptionHandler를 이용하여 coroutine 내부의 기본 catch block으로 사용할 수 있습니다.
Java에서 Thread에 사용하는 Thread.defaultUncaughtExceptionHandler와 비슷다하고 생각하면 됩니다.
추가적으로 Android에서는 기본으로 uncaughtExceptionPreHandler가 coroutine의 exception 처리를 할수있도록 설정 되어 있습니다.
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
throw AssertionError()
}
val deferred = GlobalScope.async(handler) {
throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
}
joinAll(job, deferred)
}
Caught java.lang.AssertionError
결과를 보면 launch에서 발생한 exception만 처리되었음을 알수있습니다.
async처럼 await()를 만나야 exception이 발생하는 경우에는 동작하지 않습니다.
Cancellation and exceptions
fun main() = runBlocking {
val job = launch {
val child = launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("Child is cancelled")
}
}
yield()
println("Cancelling child")
child.cancel()
child.join()
yield()
println("Parent is not cancelled")
}
job.join()
}
Child is cancelled
Parent is not cancelled
Coroutine은 취소를 제외한 다른 exception이 발생하면 부모의 corouitne까지 모두 취소 시킵니다.
이런 동작은 structured concurrency를 유지하기 위함이기 때문에, CoroutineExceptionHandler를 설정하더라도 막을 수 없습니다.
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
launch { // the first child
try {
delay(Long.MAX_VALUE)
} finally {
withContext(NonCancellable) {
println("Children are cancelled, but exception is not handled until all children terminate")
delay(100)
println("The first child finished its non cancellable block")
}
}
}
launch { // the second child
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
}
job.join()
println("End runBlocking")
}
Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
The first child finished its non cancellable block
Caught java.lang.ArithmeticException
End runBlocking
Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
The first child finished its non cancellable block
Exception in thread "main" java.lang.ArithmeticException
...
...
Exception aggregation
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception with suppressed ${exception.suppressed.contentToString()}")
}
val job = GlobalScope.launch(handler) {
launch {
try {
delay(Long.MAX_VALUE)
} finally {
throw ArithmeticException()
}
}
launch {
delay(100)
throw IOException()
}
delay(Long.MAX_VALUE)
}
job.join()
}
Caught java.io.IOException with suppressed [java.lang.ArithmeticException]
다만 CancellationException의 경우에는 throw 하더라도 handler에서 무시됩니다.
fun main() = runBlocking {//sampleStart
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught original $exception")
}
val job = GlobalScope.launch(handler) {
val inner = launch {
launch {
launch {
throw IOException()
}
}
}
try {
inner.join()
} catch (e: CancellationException) {
println("Rethrowing CancellationException with original cause")
throw e
}
}
job.join()
}
Rethrowing CancellationException with original cause
Caught original java.io.IOException
handler의 위치, GlobalScope이 아닐때의 exception 처리에 대해서는 아래 링크에서 추가적인 예제로 다룹니다.
https://tourspace.tistory.com/175
'개발이야기 > Kotlin' 카테고리의 다른 글
[Kotlin] 코틀린 - 코루틴#7 - Channels (0) | 2018.12.21 |
---|---|
[Kotlin] 코틀린 - 코루틴#6 - supervision (0) | 2018.12.12 |
[Kotlin] 코틀린 - 코루틴#4 - context와 dispatchers (0) | 2018.12.09 |
[Kotlin] 코틀린-코루틴#3 - suspending function의 구성 (0) | 2018.12.06 |
[Kotlin] 코틀린 - 코루틴#2 취소와 Timeout (0) | 2018.12.04 |