ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Coroutine] 코루틴 학습 - 8 (Exception handling)
    Java/Kotlin 2022. 5. 5. 06:56
    반응형

    Exception handling

    • 코루틴은 익셉션이 발생하면 해당 코루틴을 cancel 시키고, 해당 익셉션을 부모 코루틴으로 전파한다.
    • 익셉션을 전파받은 부모 코루틴은 자신과 자신의 자식 코루틴들을 cancel 시키고, 또 다시 자신의 부모 코루틴으로 익셉션을 전파한다.
    • 위 과정을 반복해서 최상위 코루틴까지 예외가 전파된다.
    fun main(): Unit = runBlocking {
        launch {
            launch {
                delay(1000)
                throw Error("Some error")
            }
    
            launch {
                delay(2000)
                println("출력되지 않는다.")
            }
    
            launch {
                delay(300)
                println("출력된다.")
            }
        }
    
        launch {
            delay(2000)
            println("출력되지 않는다.")
        }
    }

    • 위 코드에서 예외가 발생하는 코루틴을 try-catch로 래핑하여 감싸는 방법은 Job을 통해서 서로 커뮤니케이션이 일어나는 코루틴의 특성 때문에 cancel 이 전파되는 것을 막을 수 없다.
    fun main(): Unit = runBlocking {
        launch {
            try {
                launch {
                    delay(1000)
                    throw Error("Some error")
                }
            } catch (e: Throwable) {
                println("출력되지 않는다.")
            }
    
            launch {
                delay(2000)
                println("출력되지 않는다.")
            }
        }
    }

     

    SupervisorJob

     SupervisorJob은 자식 코루틴에게서 발생하는 cancel 을 무시하도록 하는 Job이다.

    fun main(): Unit = runBlocking {
        launch {
            val supervisorJob = SupervisorJob()
            launch(supervisorJob) {
                delay(1000)
                throw Error("Some error")
            }
    
            launch(supervisorJob) {
                delay(2000)
                println("Inner Coroutine")
            }
    
            supervisorJob.join()
        }
        launch {
            delay(3000)
            println("Outer Coroutine")
        }
    }
    // Exception in thread "main" java.lang.Error: Some error
    // Innter Coroutine
    // Outer Coroutine

     

     일반적으로 부모 코루틴을 argument로 넘기는 경우에 발생하는 실수는 아래의 코드와 같다.

    fun main(): Unit = runBlocking {
        // Bad practice
        launch(SupervisorJob()) {
            launch {
                delay(1000)
                throw Error("Some error")
            }
    
            launch {
                delay(2000)
                println("Inner Coroutine")
            }
        }
    
        delay(3000)
    }

     위 코드는 SupervisorJob이 오직 하나의 자식 코루틴만 있기 때문에 예외 처리에 도움이 되지 않는다. 아래와 같이 여러 coroutine builder의 컨텍스트로 작업을 수행하는 것이 더 좋다. 이는 자식 코루틴들이 각자 취소될 수 있지만, 서로 영향을 주어 취소시키진 않는다.

    fun main(): Unit = runBlocking {
        val job = SupervisorJob()
        launch(job) {
            delay(1000)
            throw Error("Some error")
        }
    
        launch(job) {
            delay(2000)
            println("출력된다")
        }
        job.join()
    }
    // (1 sec)
    // Exception..
    // (1 sec)
    // 출력된다

     

    supervisorScope

     예외가 전파되는 것을 멈추는 또다른 방법은 coroutine builder를 supervisorScope로 래핑하는 것이다. 이는 부모 코루틴과 연결을 유지하기 때문에 편리한 방법이다. 그리고 코루틴으로부터의 예외는 묵음처리 된다.

    fun main(): Unit = runBlocking {
        supervisorScope {
            launch {
                delay(1000)
                throw Error("Some error")
            }
    
            launch {
                delay(2000)
                println("Inner 코루틴")
            }
        }
        delay(1000)
        println("Done")
    }
    // Exception
    // Inner 코루틴
    // (1 sec)
    // Done

     supervisorScope는 일시중단 함수이며, 일시중단 함수 본문을 래핑하는데 사용할 수 있다. 이는 일반적으로 서로 독립적인 여러 작업들을 시작할 때 많이 사용한다.

     

    Await

     예외가 발생하는 케이스에서 코루틴 빌더 async는 launch와 와 다른 코루틴 빌더들과 마찬가지로 그와 관련된 부모 코루틴을 중단시킨다.  하지만 supervisorScope를 이용하면 마찬가지로 cancel이 전파되는 것을 막을 수 있다.

     아래의 코드에서는 supervisorScope 내에서 async 가 바깥으로 예외를 던지는 상황인데, 다른 async는 중단되지 않고 실행이 완료된다.

    suspend fun main() = supervisorScope {
        val str1 = async<String> {
            delay(1000)
            throw MyException()
        }
    
        val str2 = async {
            delay(2000)
            "Text2"
        }
    
        try {
            println(str1.await())
        } catch (e: MyException) {
            println(e)
        }
    
        println(str2.await())
    }
    // MyException
    // Text2

     

    CancellationException

     발생한 예외가 CancellationException의 서브클래스인 경우에는 부모 클래스로 예외를 전파하지 않고, 현재 코루틴 내에서만 예외가 발생한다.

    suspend fun main(): Unit = coroutineScope {
        launch {
            launch {
                delay(2000)
                println("출력되지 않는다.")
            }
            throw CustomNonPropagatingException
        }
        launch {
            delay(3000)
            println("출력된다.")
        }
    }

     

    Coroutine exception handler

    • CoroutineExceptionHandler를 이용하면 익셉션을 핸들링할 때 기본적으로 수행해야 하는 로직들을 처리하기 편리하다.
    • 핸들러는 예외가 전파되는 것을 막지는 않지만, 예외가 발생했을 때 수행해야 할 로직들을 설정할 수 있다. (로깅 등..)
    • 아래 코드는 CoroutineExceptionHandler와 예외의 전파를 막을 수 있는 SupervisorJob()을 이용한 예시 코드이다.
    • SupervisorJob으로 인해 예외가 전파되지 않기 때문에 두 번째 자식 코루틴이 중지되지 않고 계속 실행된다.
    fun main(): Unit = runBlocking {
        val handler = CoroutineExceptionHandler { ctx, exception ->
            println("exception - $exception")
        }
        val scope = CoroutineScope(SupervisorJob() + handler)
        scope.launch {
            delay(1000)
            throw Error("Some error")
        }
    
        scope.launch {
            delay(2000)
            println("출력된다.")
        }
        scope.coroutineContext.job.children.forEach { it.join() }
    }

     


    참고자료

    https://www.amazon.com/Kotlin-Coroutines-Deep-Marcin-Moskala/dp/8396395837

    반응형

    댓글

Designed by Tistory.