[Kotlin] Coroutine의 예외처리
by its_TIMI목차
1. Root Coroutine을 만드는 방법
2-1. launch 내부에 예외(main thread가 아닌 다른 thread에 만들어져 root Coroutine)
2-2. launch 내부에 예외(main thread에 만들어져 자식 코루틴, 하지만 supervisorjob으로 부모에게 예외 전파 안시킴)
3-1. async 내부에 예외(main thread가 아닌 다른 thread에 만들어져 root Coroutine)
3-2. async 내부에 예외(main thread에 만들어지며 runBolcking 자식 코루틴.)
4-1. 예외처리 : try-catch-finally
4-2 예외처리 : CoroutineExceptionHandler
4-3 코루틴의 예외 차별 기준
이전 포스팅들에 있는 코루틴들의 형태는 모두
run Blocking{ //부모 코루틴
launch{} //자식 코루틴
launch{} //자식 코루틴
}
이런식이었다
1. Root Coroutine을 만드는 방법
근데 이번에는 launch coroutine을 새로운 root coroutine으로 만들고 싶다면, 새로운 영역을 만들면 된다.,
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
printWithThread("START")
val job1 = CoroutineScope(Dispatchers.Default).launch { // 새로운 영역을 만들고, main이 아닌 다른 thread에서 실행될 수 있도록 해준다.
delay(1_000L)
printWithThread("job1")
}
val job2 = CoroutineScope(Dispatchers.Default).launch {
delay(1_000L)
printWithThread("job2")
}
}
이렇게 되면 각각이 root coroutine. 하지만 이대로라면 출력은
[main] START
Process finished with exit code 0
이유는 아직 정확히는 모르겠지만?
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
printWithThread("START")
val job1 = CoroutineScope(Dispatchers.Default).launch {
delay(1_000L)
printWithThread("job1")
}
val job2 = CoroutineScope(Dispatchers.Default).launch {
delay(1_000L)
printWithThread("job2")
}
job1.join()
job2.join()
}
이렇게 해주면
[main] START
[DefaultDispatcher-worker-1] job2
[DefaultDispatcher-worker-2] job1
Process finished with exit code 0
출력이 된다?! ㄴㅇㄱ
자세한 내용은 아래 글 참조함.
코틀린 코루틴 제어
이전 포스트 에서는 코루틴을 사용하기 위한 기초 개념을 알아보았습니다.
medium.com
아모튼..
2-1. launch 내부에 예외(main thread가 아닌 다른 thread에 만들어져 root Coroutine)
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val job = CoroutineScope(Dispatchers.Default).launch {
throw IllegalArgumentException()
}
delay(1_000L)
}
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.IllegalArgumentException
at coroutine.Lec05Kt$main$1$job$1.invokeSuspend(Lec05.kt:7)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [CoroutineId(2), "coroutine#2":StandaloneCoroutine{Cancelling}@267ee0fe, Dispatchers.Default]
Process finished with exit code 0
launch는 내부에서 예외가 발생시 - 이렇게 냅다 예외를 출력하고 종료되어벌임
2-2. launch 내부에 예외(main thread에 만들어져 자식 코루틴, 하지만 supervisorjob으로 부모에게 예외 전파 안시킴)
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val job = launch(SupervisorJob()) {
throw IllegalArgumentException()
}
delay(1_000L)
}
Exception in thread "main @coroutine#2" java.lang.IllegalArgumentException
at coroutine.Lec05Kt$main$1$job$1.invokeSuspend(Lec05.kt:7)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at coroutine.Lec05Kt.main(Lec05.kt:5)
at coroutine.Lec05Kt.main(Lec05.kt)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [CoroutineId(2), "coroutine#2":StandaloneCoroutine{Cancelling}@6debcae2, BlockingEventLoop@5ba23b66]
Process finished with exit code 0
하지만 launch()는 단독으로 쓰이든 뭐든 예외 바생시 냅다 예외 출력하고 종료되나보다!
3-1. async 내부에 예외(main thread가 아닌 다른 thread에 만들어져 root Coroutine)
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val job = CoroutineScope(Dispatchers.Default).async { // job은 deferred 객체. deferred 객체는 job interface를 구현함
throw IllegalArgumentException()
}
delay(1_000L)
}
Process finished with exit code 0
async는 내부에서 예외가 발생시 이렇게 예외를 출력하지 않고 종료됨.
예외를 확인하고 싶다면 아래와 같이 await()을 사용해주면 된다.
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val job = CoroutineScope(Dispatchers.Default).async {
throw IllegalArgumentException()
}
delay(1_000L)
job.await()
}
Exception in thread "main" java.lang.IllegalArgumentException
at coroutine.Lec05Kt$main$1$job$1.invokeSuspend(Lec05.kt:7)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:46)
at coroutine.Lec05Kt$main$1.invokeSuspend(Lec05.kt:11)
Caused by: java.lang.IllegalArgumentException
at coroutine.Lec05Kt$main$1$job$1.invokeSuspend(Lec05.kt:7)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Process finished with exit code 1
2,3번 내용 정리
launch와 async의 예외 발생 차이(main thread가 아닌 다른 thread에 만들어져 각각이 root Coroutine일 경우)
- launch : 예외가 발생하면, 예외를 추력하고 코루틴이 종료된다.
- async : 예외가 발생해도, 예외를 출력하지 않는다. 예외를 확인하고싶으면 await()을 사용해야한다.
- async가 root coroutine이므로 부모 코루틴이 없고, 따라서 예외가 전파되지 않으며 바로 출력되지 않음.
3-2. async 내부에 예외(main thread에 만들어지며 runBolcking 자식 코루틴.)
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val job = async {
throw IllegalArgumentException()
}
delay(1_000L)
}
Exception in thread "main" java.lang.IllegalArgumentException
at coroutine.Lec05Kt$main$1$job$1.invokeSuspend(Lec05.kt:7)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at coroutine.Lec05Kt.main(Lec05.kt:5)
at coroutine.Lec05Kt.main(Lec05.kt)
Process finished with exit code 1
async에다가 던져도 출력되고 종료된다?!
는 자식 코루틴(launch)의 예외는 부모 코루틴(runBlocking)으로 전파되므로
부모코루틴(runBlocking)의 예외처리 방식 따라감.
-> 만약 자식 코루틴의 예외를 부모에게 전파하고싶지 않다면
SupervisorJob() 사용하면 됨.
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val job = async(SupervisorJob()) {
throw IllegalArgumentException()
}
delay(1_000L)
// job.await()
}
Process finished with exit code 0
자식 코루틴의 예외를 부모 코루틴에게 전파시키지 않을 수 있다.
물론 SupervisorJob()을 사용했더라도, 루트 코루틴 async 처럼 await()을 사용하여 출력하는 방법도 있음
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val job = async(SupervisorJob()) {
throw IllegalArgumentException()
}
delay(1_000L)
job.await()
}
Exception in thread "main" java.lang.IllegalArgumentException
at coroutine.Lec05Kt$main$1$job$1.invokeSuspend(Lec05.kt:7)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:46)
at coroutine.Lec05Kt$main$1.invokeSuspend(Lec05.kt:11)
Caused by: java.lang.IllegalArgumentException
at coroutine.Lec05Kt$main$1$job$1.invokeSuspend(Lec05.kt:7)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:280)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at coroutine.Lec05Kt.main(Lec05.kt:5)
at coroutine.Lec05Kt.main(Lec05.kt)
Process finished with exit code 1
4-1. 예외처리 - try-catch-finally
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val job = launch(SupervisorJob()) {
try{
throw IllegalArgumentException()//예외는 던져지지만
} catch (e: IllegalArgumentException){//뇸 먹어버렸기 때뮤네 이 코루틴은 예외를 던진게 아니게됨
// 이곳에서 필요한 처리를 하고 예외를 던지거나 할 수 있게됨
printWithThread("일단 에러는 잡았다-_-그러니 정상 종료.")
}
}
}
[main @coroutine#2] 일단 에러는 잡았다-_-그러니 정상 종료.
Process finished with exit code 0
4-2 예외처리 - CoroutineExceptionHandler
CoroutineExceptionHandler는 try - catch - finally와 달리 예외 발생 이후 에러 로깅 / 에러메세지 전송 등에 활용된다.
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val exceptionHandler = CoroutineExceptionHandler { _, _ ->
printWithThread("에러가 발생했어요~!")
}
val job = CoroutineScope(Dispatchers.Default).launch(exceptionHandler) {
throw IllegalArgumentException()
}
delay(1_000L)
}
[DefaultDispatcher-worker-1 @coroutine#2] 에러가 발생했어요~!
Process finished with exit code 0
+ 리빙 포인트 : kotlin의 unit은 java의 void와 같다
만약 예외까지 다시 던지고 싶다면
라잌디스~
package coroutine
import kotlinx.coroutines.*
fun main(): Unit = runBlocking {
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
printWithThread("에러가 발생했어요~!")
throw throwable
}
val job = CoroutineScope(Dispatchers.Default).launch(exceptionHandler) {
throw IllegalArgumentException()
}
delay(1_000L)
}
[DefaultDispatcher-worker-1 @coroutine#2] 에러가 발생했어요~!
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.IllegalArgumentException
at coroutine.Lec05Kt$main$1$job$1.invokeSuspend(Lec05.kt:13)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [coroutine.Lec05Kt$main$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@781f9309, CoroutineId(2), "coroutine#2":StandaloneCoroutine{Cancelling}@49f212ff, Dispatchers.Default]
주의할 점
- launch에만 적용 가능
- 부모 코루틴이 있으면 동작 x -> 그래서 별도의 스레드에다가 만들었던것 라잌
val job = CoroutineScope(Dispatchers.Default).launch(exceptionHandler) {
4-3 코틀린의 예외 차별 기준
1) 발생한 예외가 CancellationException
- 취소로 간주하고 부모 코루틴에게 전파하지 않음
2) 나머지 예외
- 실패로 간주하고 부모 코루틴에게 전파함
출처 : 최태현 센세의 2시간 코루틴 뚝딱
'Kotlin' 카테고리의 다른 글
코틀린(Kotlin)으로 배우는 기본 자료구조 정리: 스택, 큐, 리스트, 해시맵 (2) | 2024.12.09 |
---|---|
[Kotlin] Coroutine - Structured Concurrency (0) | 2024.01.12 |
[Kotlin] Coroutine - 취소 (0) | 2024.01.11 |
[Kotlin] Coroutine - async() (1) | 2024.01.09 |
[Kotlin] Coroutine 디버깅 옵션 (1) | 2024.01.09 |
블로그의 정보
Dev_TIMI
its_TIMI