Coroutine에서 비동기 코드에서의 반환 값이 필요할 때,
코루틴 빌더인 async와 await()함수를 사용합니다.
하지만 이 API들은 Exception을 핸들링 할 때 주의가 필요한데요.
오늘은 async와 await() 사용시,
Exception을 핸들링 하는 방법에 대해 알아보겠습니다.
1. async 예외 전파 이해하기
코루틴 빌더인 async와 await함수를 사용시,
Exception핸들링을 하려면,
코루틴의 예외 전파방식에 대해 이해해야 하는데요.
먼저 보아야 할 것이,
코루틴 빌더인 async의 리턴타입입니다.
아래와 같이 Deferred타입인데요.
이 타입은 아래 보이는 것처럼, Job을 상속받고 있습니다.
코루틴 빌더인 async는,
Job을 통해 부모-자식 관계가 형성되구요.
Exception도 이를 통해 전파됩니다.
(참고로 이러한 예외전파는 다른 코루틴 빌더인 launch에서도 동일하게 발생합니다.)
한가지 더 알아둘 점은,
Deferred가 async 블록 내부에서 예외가 발생하면 이를 저장해두었다가,
await()가 호출되는 시점에,
다시 throw한다는 사실입니다.
복잡한 애기 같기는 한데,
"그럼, await()호출시점에 try...catch하면 되겠군"
하는 생각이 들 수 있습니다.
아래 코드를 볼까요?
fun main() = runBlocking {
coroutineScope {
val deferred = async {
throw RuntimeException("예외 발생!")
}
try {
println("await 직전")
deferred.await() // await가 다시 throw
} catch (e: Exception) {
println("await 예외 잡힘: ${e.message}")
}
}
println("완료")
}
실행해 보면, 재미있게도 아래와 같은 결과가 나옵니다.
왜 저렇게 된걸까요?
Deferred가 가지고 있다가,
await()호출시에 throw한다고 했는데 말이지요.
이는 async블록 내부에서 Exception발생시,
Deferred가 이를 저장했다가 await() 호출 시 throw하고,
그 후에 Job을 통해 부모에게도 전파되기 때문입니다.
부모에게 전파가 되도록 이미 결정되었기 때문입니다.
await()에서 throw한 예외는 try-catch로 잡혔지만,
이후 Job을 통해 부모에게 전파된 동일한 Exception으로 인해 crash가 발생했습니다.
자 이제 try...catch를 어디서 해야할 지 감이 오시나요?
어떻게 해야하는지 아래에서 보겠습니다.
2. 어디서 catch 해야할까?
위에서 본 코루틴의 특수성을 고려해,
async와 await를 사용할 경우,
어디에서 catch해야할까요?
아래와 같이 async블록내부에서 try...catch를 해 주어야 합니다.
이렇게 해서, Job을 통해 부모에게 Exception이 전파되는 것을 막고,
앱이 Crash되는 것을 막을 수 있습니다.
fun main() = runBlocking {
coroutineScope {
val deferred = async {
try {
throw RuntimeException("예외 발생!")
} catch (e: Exception) {
println("await의 예외 잡힘: ${e.message}")
}
}
println("await 직전")
deferred.await() // await가 다시 throw
}
println("완료")
}
실행해보면,
아래와 같이 Exception이 catch되어,
crash되지 않았습니다.
부모에게 전파되기 전에,
async블록에서,
Exception을 처리해 주었기 때문입니다.
3. supervisorScope를 사용하기
async블록안에서 try...catch하는 방법외에도,
부모에게 Exception이 전파되지 않도록 할 수 있는데요.
바로 supervisorScope를 사용하는 방법입니다.
supervisocrScope는 supervisorJob()을 사용하는 coroutineScope인데요.
supervisorScope를 사용하면,
자식에게 일어난 Exception으로 인해,
부모인 supervisorScope가 fail되지 않고,
다른 자식에게도 영향을 주지 않습니다.
(다만, supervisorScope블록 내의 Exception은 자신과 자식들에게 영향을 줍니다
자식들에게 일어난 Exception이 자신과 자신의 다른 자식들에게 영향을 주지 않는다는 것 입니다.)
그래서 아래와 같이,
supervisorScope안에 있을경우에는,
supervisorScope가 자식의 Exception의 영향을 받지 않는데요.
그래서 자식인 async블록의 밖에 try...catch를 해 주어도 부모로 전파되지 않습니다.
supervisorScope { // 부모 scope
val deferred = async { // 자식 코루틴
throw RuntimeException("에러 발생!")
}
try {
deferred.await() // 여기서 예외를 처리할 수 있음
} catch (e: Exception) {
println("예외 처리됨: ${e.message}")
}
}
이상으로 Coroutine 빌더인 Async와 await 사용시 Exception Handling 방법에 대해서 알아보았습니다.
'Android 개발 > Coroutine , Flow, Channel' 카테고리의 다른 글
코루틴 Flow vs StateFlow vs SharedFlow vs LiveData 총정리 하기 (2) | 2024.12.30 |
---|---|
StateFlow vs SharedFlow 를 비교해보자 #이벤트 핸들링 (1) | 2023.05.06 |
SharedFlow 에 대한 총정리 # Buffer Replay tryEmit Kotlin Coroutine (2) | 2023.05.04 |
Flow 결합연산자 combine , zip , merge 비교 총정리 # Kotlin Coroutine (0) | 2023.05.03 |
onEach vs onStart 비교 정리 # Kotlin Coroutine Flow (0) | 2023.05.03 |
Coroutine suspend 동작에 관한 좋은 예와 잘못된 예 # 비동기 (0) | 2023.04.18 |
flatMapLatest 이용해서 값이 들어오는 것을 기다리기 # Coroutine (0) | 2023.04.18 |
함수 실행 시간 측정 후 Delay 사용하기 (0) | 2023.04.14 |
MutableStateFlow 이용한 로딩 후 로딩 완료 기다리기 구현 방법 (0) | 2023.04.08 |
StateFlow 정리 # Android Kotlin Coroutine getStateFlow StateIn (0) | 2022.10.12 |
댓글