Coroutine 책을 읽던 중 SupervisorJob()에 대해 알게 되어 정리하게 되었다. 공식문서만 잘 읽고 넘어가려 했으나
이것만 읽고는 이해가 잘 되지 않았다.. 그래서 추가로 알아본 내용을 정리하고자 한다.
참고로 나는 이 책을 읽고 노션에 정리하고 있으니 많이 봐주길 바란다.
결론부터 말하면
SupervisorJob()은 자식 코루틴의 예외로부터 부모 코루틴을 보호해준다.
이는 다음과 같이 활용할 수 있다.
suspend fun getName(): String {
delay(500)
return "Seunghoon"
}
suspend fun getAge(): Int {
delay(300)
throw Error("what?")
return 19
}
suspend fun getJob(): String {
delay(100)
return "Android Developer"
}
이렇게 3개의 함수가 있다고 하자.
일반적으로 실행했을 때 순서는 getJob() -> getAge() -> getName()이 될 것이다.
하지만 getAge()는 예외를 던지고 있다.
fun main(): Unit = runBlocking {
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
scope.launch {
println(getName())
}
scope.launch {
println(getAge())
}
scope.launch {
println(getJob())
}
}
delay(1000)
}
위 방식처럼 각 함수들을 호출했을 때 실행결과가 어떻게 될까?
-> 더 내리기 전에 스스로 한 번 생각해보길 바란다.
첫 번째 함수인 getJob()만 성공적으로 실행되고 에러가 발생한다.
그리고 3번째 함수는 정상적으로 실행되지 않는다.
코루틴의 구조화된 동시성이라는 특징 덕분에 이같은 상황이 발생한 것이다.
여기서 길게 다루기엔 글이 길어질 것 같아서 짧게 설명하고 넘어가도록 하겠다.
하나의 코루틴이 예외로 취소되어도 다른 코루틴은 정상적으로 동작하게 하고 싶은데..
-> 이때 우리는 위에서 언급한 SupervisorJob()을 사용할 수 있다.
fun main(): Unit = runBlocking {
val scope = CoroutineScope(SupervisorJob())
scope.launch {
scope.launch {
println(getName())
}
scope.launch {
println(getAge())
}
scope.launch {
println(getJob())
}
}
delay(1000)
}
맙소사! scope의 컨텍스트만 바꿔줬는데 getName() 함수가 실행되었다!
이 말은, SupervisorJob()은 예외가 위로 (부모로) 전파되는것을 방지해준다는 것이다!
getAge()를 호출한 코루틴에서 예외가 발생했지만 이 예외가 가장 바깥 코루틴까지 영향을 미치지 않았다. 따라서 자식 코루틴들도 종료되지 않았고, 0.5초후에 getName()이 정상적으로 실행될 수 있었다.
우리는 위에서 이런 형식으로 스코프에 SupervisorJob()을 전달하여 사용하였다.
val scope = CoroutineScope(SupervisorJob())
이것도 좋지만 매번 SupervisorJob()을 생성하기는 귀찮다.
이럴땐 supervisorScope{}를 써보자.
private suspend fun main() = supervisorScope {
launch {
println(getName())
}
launch {
println(getAge())
}
launch {
println(getJob())
}
}
코드가 훨씬 간결해졌다!
평소에는 생각해본적이 없는데 글을 쓰다가 갑자기 궁금해졌다.
예외로부터 부모 코루틴을 보호할 수 있는데 SupervisorJob()으로 도배해서 쓰면 좋은거 아닌가? 사람들이 이렇게 쓰지 않는데는 이유가 있다.
SupervisorJob()은 runCatching{}에서 예외를 무시한다.
fun main(): Unit = runBlocking {
kotlin.runCatching {
main1()
}.onSuccess {
println("Success")
}.onFailure {
println(it)
}
delay(1000)
}
private suspend fun main1() = supervisorScope {
launch {
println(getName())
}
launch {
println(getAge())
}
launch {
println(getJob())
}
}
위에서 사용한 함수를 runCatching으로 한 번 감싸보았는데, 예외가 콘솔창에 찍히기만 하고 onSuccess로 잡혔다.
SupervisorJob을 사용한 코루틴은 상위 코루틴에서 예외를 잡을 수 없다.
자식 코루틴을 부모 코루틴으로부터 독립시키고 싶다면 SupervisorJob()을 사용하자
단, SupervisorJob을 사용하면 상위 코루틴에서 예외를 잡을 수가 없다.
따라서 예외 발생 시 수행해야 할 작업이 있다면 그냥 coroutineScope + runCatching을 사용하는것이 좋을 것 같다.
ps.피드백은 언제나 환영이다.