[Kotlin] coroutineScope와 supervisorScope
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