티스토리 뷰
Exception
Coroutine에서 기본적으로 Exception 처리 방식은 전달(Propagation)과 노출(Expose)가 있습니다. launch나 actor같은 빌더는 예외가 부모 Coroutine으로 전달되고, async나 produce같은 빌더는 결과 값을 사용할 때 Exception이 노출됩니다.
import kotlinx.coroutines.*
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val job = GlobalScope.launch { // root coroutine with 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 { // root coroutine with 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")
}
}
Throwing exception from launch
Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException
Joined failed job
Throwing exception from async
Caught ArithmeticException
실제로 launch 빌더로 생성된 Coroutine은 Exception을 바로 발생시키지만, async 빌더로 생성된 Coroutine은 await()을 만나야 Exception을 발생시킵니다.
CoroutineExceptionHandler
예외가 발생할 수 있는 코드에 try~catch를 직접 설정할 수 있지만 모든 Coroutine에 코드를 작성하면 중복 코드가 많아 질 수 있습니다. 이럴 때 CoroutineExceptionHandler를 사용하여 처리할 수 있습니다. Scope를 생성할 때 Context에 Handler를 추가하면 됩니다.
* Thread.uncaughtExceptionHandler 방식과 비슷합니다.
fun exceptionHandler() {
val exceptionHandler = CoroutineExceptionHandler { context, exception ->
Log.d("ExceptionHandler", "Exception : $exception", exception)
}
CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
throw Exception("Hello World")
}
}
Propagation vs Exposed
CoroutineExceptionHandler를 만들고 Job과 Deferred를 Handling 해봅시다. 결과는 Job은 CoroutineExceptionHandler가 처리하지만 Deferred는 처리하지 않습니다. 그 이유는 Deferred는 await()을 통해 Exception을 노출해야 받기 때문입니다.
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
}
val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
throw AssertionError()
}
val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
}
joinAll(job, deferred)
CoroutineExceptionHandler got java.lang.AssertionError
CancellationException
취소는 Exception과 관련이 있습니다. Coroutine은 내부적으로 cancel()할 때 CancellationException을 발생시킵니다. 하지만 이 Exception은 Handler에 전달되지 않으며 부모에게도 전파하지 않습니다.
* try-catch문으로 Exception을 catch 할 수 있습니다.
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $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()
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
CoroutineExceptionHandler got java.lang.ArithmeticException
만약 CancellationException이 부모나 형제 Coroutine에 영향을 주어 Exception을 전파하거나 취소시키면 본래 Coroutine Cancel의 의미를 잃어버립니다.
Exception Handle
여러개의 Child를 가진 Coroutine에서 Exception이 발생하면 처음으로 발생한 Exception을 처리하게 된다. 만약 여러 Exception이 발생하면 Suppressed Exception형식으로 전달됩니다. (CancellationException은 무시된다.)
Coroutine에서 취소를 제외한 Exception이 발생하면 부모의 Coroutine을 취소시키고, CoroutineExceptionHandler를 사용해도 막을 수 없습니다.
import kotlinx.coroutines.*
import java.io.*
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
}
val job = GlobalScope.launch(handler) {
launch {
try {
delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
} finally {
throw ArithmeticException() // the second exception
}
}
launch {
delay(100)
throw IOException() // the first exception
}
delay(Long.MAX_VALUE)
}
job.join()
}
CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
'Android > Coroutine' 카테고리의 다른 글
Android Coroutine - 동기화 (0) | 2022.01.19 |
---|---|
Android Coroutine - Supervision (0) | 2022.01.16 |
Android Coroutine - Job Lifecycle (0) | 2022.01.15 |
Android Coroutine - Cancel (0) | 2022.01.08 |
Android Coroutine - Coroutine Builder (0) | 2022.01.07 |
- Total
- Today
- Yesterday
- 코루틴
- Exception
- gradle
- isActive
- Widget
- viewmodel
- TDD
- observable
- Kotlin
- Flowable
- clean code
- Flutter
- DART
- CancellationException
- 클린 코드
- ViewModelProvider
- ConcatAdapter
- DSL
- ConcatAdapter.Config
- null
- 함수
- 보이스카우트 규칙
- commit
- git
- Coroutine
- 클린코드
- ViewModelStoreOwner
- rxjava
- Android
- 연산자
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |