Dev_TIMI

[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시간 코루틴 뚝딱

반응형

블로그의 정보

Dev_TIMI

its_TIMI

활동하기