일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Android
- withContext
- monotone stack
- Product Flavor
- collectLatest
- flowon
- java
- TOSS 과제
- conflate
- Next Challenge
- 백준
- hotStream
- Kotlin
- ServerDrivenUI
- google play console
- coroutine
- coldStream
- 릴리즈 키해시
- Algorithm
- KAKAO
- SDUI
- app-distribution
- 백준2309
- coroutinecontext
- Flow
- coroutinescope
- 안드로이드
- cancellationException
- Advanced LCA
- ShapeableImageView
- Today
- Total
루피도 코딩한다
[Coroutine Basics 5] 코루틴의 예외 처리와 Job의 상태 변화 본문
이전 글에서 CancellationException()
을 통해 코루틴을 취소하는 방법에 대해 알아보았다.
그러면 코루틴에서 예외가 발생할 때 무슨일이 벌어지고, 어떻게 예외처리를 해야할까?
코루틴에서는 예외가 발생하면 '부모 코루틴'에게 예외가 전파된다.
따라서 첫번째로 코루틴의 계층 관계에 대해 정리해보도록 하자!
fun main(): Unit = runBlocking {
val job1 = launch {
delay(1_000L)
printWithThread("Job 1")
}
val job2 = launch {
delay(1_000L)
printWithThread("Job 2")
}
}
위 코드의 경우 runBlocking이라는 최상위(aka. root) 코루틴에 launch로 만들어진 두개의 자식 코루틴이 있는 구조이다.
이 job1과 job2를 runBlocking과 독립적으로 만들려면 어떻게 해야할까?
CoroutineScope
함수를 이용해 새로운 영역을 만들고 해당 영역에서 launch를 호출하면 된다.
(Android의 경우 ViewModelScope도 root 코루틴을 생성한다)
계층이 있는 코루틴 구조 | 각각이 root 코루틴 |
---|---|
![]() |
![]() |
Case1. 최상위 코루틴에서 예외 발생한 경우
launch
: 예외 발생 시 예외 출력 후 코루틴 종료async
: 예외 발생하더라도 예외 출력 안함 (정상 종료)async
내부에서 예외 발생 후await()
호출 : 예외 출력 후 코루틴 종료
Case2. 자식 코루틴에서 예외 발생한 경우
launch
,async
둘 다 예외 출력 후 die..- 코루틴 안에서 발생한 예외가 부모 코루틴으로 '전파'되기 때문이다
- 자식 코루틴의 예외가 runBlocking으로 열린 부모 코루틴으로 이동되며, 부모코루틴도 취소하는 절차에 들어가게 된다.
Case3. 자식 코루틴에서 예외가 발생했지만, 부모에게 전파하지 않는 경우
async
: 자식 코루틴을 생성할때SupervisorJob()
을 넣어주면 예외가 전파되지 않는다.단, 자식 코루틴의 job을 await()로 조회하는 경우에 예외가 발생하게 된다.
fun main() = runBlocking<Unit> { val scope = CoroutineScope(Dispatchers.IO + SupervisorJob() + ceh) val job1 = scope.launch { printRandom1() } val job2 = scope.launch { printRandom2() } joinAll(job1, job2) }
SupervisorScope
: 코루틴 스코프와 슈퍼바이저 잡을 합친듯 한 SupervisorScope도 존재한다자식의 실패가 부모에게 전달되지 않기 때문에, 자식수준에서 예외를 처리해 주어야 한다
import kotlin.random.Random import kotlin.system.* import kotlinx.coroutines.* suspend fun printRandom1() { delay(1000L) println(Random.nextInt(0, 500)) } suspend fun printRandom2() { delay(500L) throw ArithmeticException() } suspend fun supervisoredFunc() = supervisorScope { ✅ launch { printRandom1() } launch(ceh) { printRandom2() } ✅ } val ceh = CoroutineExceptionHandler { _, exception -> println("Something happend: $exception") } fun main() = runBlocking<Unit> { val scope = CoroutineScope(Dispatchers.IO) val job = scope.launch { supervisoredFunc() } job.join() }
그러면 async는 supervisorjob을 이용해 부모 코루틴에 전파되는 것을 막았다고 해보자.
launch의 경우 어떻게 처리해야할까? launch에서 발생한 에러를 핸들링 하려면 두가지 방식이 있다.
1. try-catch 활용하기
fun main(): Unit = runBlocking {
val job = launch() { // async도 마찬가지로 try-catch를 적용할 수 있다
try {
throw IllegalArgumentException()
} catch (e: IllegalArgumentException) {
printWithThread("end")
}
}
}
2. CoroutineExcpetionHandler
CoroutineExceptionHandler
객체는 코루틴의 구성 요소와 발생한 예외를 파라미터로 받을 수 있다- launch와 함께 쓰이며, 부모 코루틴이 있으면 동작하지 않는다
Excpetion에 대해 얘기해보자
- CancellationException : 취소로 인식되며 부모 코루틴에게 전파하지 않음
- 나머지 예외 : 실패로 간주하고 부모 코루틴에 전파됨
State Machine을 보면, Cancellation Exception이 발생했을 때
NEW
-> ACTIVE
-> CANCELLING
-> CANCELLED
상태가 된다.
만약 정상 처리 과정이라면
NEW
-> ACTIVE
-> COMPLETING
-> COMPLETED
상태가 된다.
위 Life Cycle에 대한 자세한 내용은 다음 포스팅에서~
'Coroutine' 카테고리의 다른 글
[Coroutine Basics 7] Suspending function (1) | 2024.01.24 |
---|---|
[Coroutine Basics 6] Structured Concurrency, CoroutineScope, CoroutineContext (0) | 2024.01.22 |
[Coroutine Basics 4] Coroutine Cacelation (0) | 2024.01.19 |
[Coroutine Basics 3] Coroutine Builder and Job (0) | 2024.01.18 |
[Coroutine Basics 2] Android에서의 Thread와 Coroutine (0) | 2024.01.17 |