Backend/Kotlin

테스트 코드로 이해해보는 코루틴 예외처리

findmypiece 2023. 2. 11. 01:02
728x90
@Test
fun `코루틴은 논블로킹으로 실행된다`(){
    /*
    코루틴 내부의 코루틴은 각각 논블로킹으로 처리된다.
    단, 스레풀에서 돌렸을 때 이야기..
    메인스레드 하나에서 돌리면 작업자가 한명이니 당연히 순차처리된다.
     */
    runBlocking(Dispatchers.IO) {

        launch {
            repeat(1000){
                println("1111111")
            }
        }
        launch {
            repeat(1000){
                println("2222222")
            }
        }
    }
}

@Test
fun `코루틴은 기본적으로 예외가 부모로 전파된다`(){
    /*
    예외가 부모로 전파되면 부모가 취소되고 결국 다른 형제 코루틴도 취소된다.
    launch, async 동일
     */
    runBlocking(Dispatchers.IO) {

        launch{
            repeat(100){
                println("첫번째 $it")
                if(it == 2){
                    throw RuntimeException("1111111 Error")
                }
            }
        }

        launch {
            repeat(100){
                println("두번째 $it")
            }
        }
    }
}

@Test
fun `예외를 부모로 전파하지 않으려면 SupervisorJob을 사용해야 한다`(){

    runBlocking(Dispatchers.IO) {

        launch(SupervisorJob()) {
            repeat(100){
                println("첫번째 $it")
                if(it == 2){
                    throw RuntimeException("1111111 Error")
                }
            }
        }

        launch {
            repeat(100){
                println("두번째 $it")
            }
        }
    }
}

@Test
fun `예외를 부모로 전파하지 않는 방법으로 코루틴 내부를 try catch 로 래핑하는 방법도 있긴하다`(){

    runBlocking(Dispatchers.IO) {

        launch {
            try{
                repeat(100){
                    println("첫번째 $it")
                    if(it == 2){
                        throw RuntimeException("1111111 Error")
                    }
                }
            }catch (e: Exception){
                println("캐치함 ${e.message}")
            }
        }

        launch {
            repeat(100){
                println("두번째 $it")
            }
        }
    }
}

@Test
fun `하지만 코루틴 바깥에 정의된 try catch 에서는 예외를 잡지 못한다`(){
    /**
     * async 도 마찬가지다.
     * 이러한 특성 때문에 try catch 로 예외 전파를 막으려면
     * 모든 코루틴 내부를 try catch 로 래핑해야 하는 문제가 생긴다.
     */
    runBlocking(Dispatchers.IO) {

        try{
            launch {
                repeat(100){
                    println("첫번째 $it")
                    if(it == 2){
                        throw RuntimeException("1111111 Error")
                    }
                }
            }
        }catch (e: Exception){
            println("캐치함 ${e.message}")
        }

        launch {
            repeat(100){
                println("두번째 $it")
            }
        }
    }
}

@Test
fun `supervisorScope 블록을 이용하면 해당 블록 내의 코루틴은 모두 SupervisorJob을 사용하게 된다`(){
    runBlocking(Dispatchers.IO) {
        supervisorScope{
            launch{
                repeat(100){
                    println("첫번째 $it")
                    if(it == 2){
                        throw RuntimeException("1111111 Error")
                    }
                }
            }

            launch {
                repeat(100){
                    println("두번째 $it")
                }
            }
        }
    }
}

/**
 * 예외전파는 SupervisorJob 으로 막았는데 핸들링은 어떻게 할 수 있을까?
 * 예외를 핸들링 하려면 어쨋든 예외를 잡아야 한다.
 */

@Test
fun `launch 에서 발생된 예외를 캐치하려면 CoroutineExceptionHandler 람다를 사용해야 한다`(){
    runBlocking(Dispatchers.IO) {
        supervisorScope{
            launch(
                CoroutineExceptionHandler{context, ex ->
                    println("${ex.message} 에 대한 후처리입니다")
                }
            ){
                repeat(100){
                    println("첫번째 $it")
                    if(it == 2){
                        throw RuntimeException("1111111 Error")
                    }
                }
            }

            launch {
                repeat(100){
                    println("두번째 $it")
                }
            }
        }
    }
}

@Test
fun `async 에서 발생된 예외는 await() 호출시 try catch 로 캐치하면 된다`(){
    runBlocking(Dispatchers.IO) {
        supervisorScope{

            try {
                async{
                    repeat(100){
                        println("첫번째 $it")
                        if(it == 2){
                            throw RuntimeException("1111111 Error")
                        }
                    }
                }.await()
            }catch(e: Exception){
                println("${e.message} 에 대한 후처리입니다")
            }

            launch {
                repeat(100){
                    println("두번째 $it")
                }
            }
        }
    }
}
728x90