Programming Language/Kotlin

[Kotlin] coroutineScope와 supervisorScope

Tenacity_Dev 2024. 1. 20. 15:54
728x90

coroutineScope와 supervisorScope가 각각 무엇인지 그리고 차이가 무엇인지에 대해서 알아보자.

 

coroutineScope

coroutineScope는 Kotlin의 코루틴에서 사용되는 스코프 중 하나로, 여러 개의 코루틴이 모두 완료될 때까지 대기하거나, 그 중 하나라도 예외가 발생하면 모든 코루틴을 즉시 취소하는 기능을 제공한다.

coroutineScope 함수는 suspend 함수 내에서만 사용될 수 있으며, coroutineScope 내에서 시작된 코루틴이 완료되거나 예외가 발생할 때까지 현재 코루틴을 일시 중단한다.

import kotlinx.coroutines.*

suspend fun main() {
    try {
        coroutineScope {
            // 이 부분은 coroutineScope 내의 코루틴입니다.
            launch {
                delay(100)
                println("This will be printed")
            }

            // 예외를 발생시킵니다.
            throw RuntimeException("Exception in coroutineScope")
        }
    } catch (e: Exception) {
        println("Caught an exception: $e")
    }
}

위의 예제에서 coroutineScope 내에서 발생한 예외는 부모 코루틴에 영향을 미치고, launch 블록 내의 코루틴은 정상적으로 실행되지 않는다. 이러한 특성 때문에, coroutineScope는 부모 코루틴을 예외로부터 보호하면서 하위 코루틴들이 완료되기를 기다릴 때 유용하게 사용된다.

 

supervisorScope

supervisorScope는 Kotlin의 코루틴에서 사용되는 스코프 중 하나로, 부모 스코프의 코루틴이 예외로 인해 취소되어도 해당 supervisorScope 내의 코루틴은 종료되지 않고 계속 실행될 수 있도록 하는 기능을 제공한다.

일반적으로, 부모 코루틴이 예외로 인해 취소되면 자식 코루틴도 취소됩니다. 그러나 supervisorScope를 사용하면 부모 코루틴이 취소되어도 그 하위에 있는 코루틴은 영향을 받지 않고 계속 실행된다.

import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        supervisorScope {
            // 이 부분은 supervisorScope 내의 코루틴입니다.
            launch {
                delay(100)
                println("This will be printed")
            }

            // 예외를 발생시킵니다.
            throw RuntimeException("Exception in supervisorScope")
        }
    } catch (e: Exception) {
        println("Caught an exception: $e")
    }
}

위의 예제에서 supervisorScope 내에서 발생한 예외는 부모 코루틴에 영향을 미치지 않고, launch 블록 내의 코루틴은 정상적으로 실행된다.

 

coroutineScope와 supervisorScope의 차이

다음과 같이 두 개의 네트워크 호출이 있다고 가정한다.

  • getUsers()
  • getMoreUsers()

우리는 서버에서 데이터를 가져오기 위해 이 두 개의 네트워크 호출을 병렬로 수행하려고 한다.

launch {
    try {
        val usersDeferred = async {  getUsers() }
        val moreUsersDeferred = async { getMoreUsers() }
        val users = usersDeferred.await()
        val moreUsers = moreUsersDeferred.await()
    } catch (exception: Exception) {
        // handle exception
    }
}

위 코드에서는 가장 큰 문제에 직면하게 된다.

두 네트워크 호출 중 하나라도 오류가 발생하면 애플리케이션이 충돌하게 됩니다! , catch 블록으로 이동 하지 않는다.

 

그렇다면 어떻게 해결해야할까?

우선 coroutineScope를 이용해보자.

launch {
    try {
        coroutineScope {
            val usersDeferred = async {  getUsers() }
            val moreUsersDeferred = async { getMoreUsers() }
            val users = usersDeferred.await()
            val moreUsers = moreUsersDeferred.await()
        }
    } catch (exception: Exception) {
        // handle exception
    }
}

위 코드처럼 작성한다면 이제 네트워크 오류가 발생하면 catch 블록으로 이동한다.

 

하지만 만약에 하나가 실패하더라도 나머지 하나를 실행시키고 싶다면???

 

그렇다면 아래를 참고해보자.

이것은 supervisorScope를 이용한 것이다.

launch {
    supervisorScope {
        val usersDeferred = async { getUsers() }
        val moreUsersDeferred = async { getMoreUsers() }
        val users = try {
            usersDeferred.await()
        } catch (e: Exception) {
            emptyList<User>()
        }
        val moreUsers = try {
            moreUsersDeferred.await()
        } catch (e: Exception) {
            emptyList<User>()
        }
    }
}

실패한 네트워크 호출에 대해 빈 목록을 반환하고 다른 네트워크 호출의 응답을 계속하고 싶다고 한다면 위처럼 작성하면 되는 것이다.

 

정리

  • coroutineScope 자식 중 하나라도 실패할 때마다 취소된다.
  • supervisorScope 다른 자식 중 하나가 실패하더라도 다른 자식을 취소하지 않는다.

 

참고

https://amitshekhar.me/blog/coroutinescope-vs-supervisorscope

 

coroutineScope vs supervisorScope

In this blog, we will learn about the coroutineScope vs supervisorScope of Kotlin Coroutines.

amitshekhar.me

 

728x90