Scope의 자식 Scope 에서 Exception 이 났을 때 동작
아무런 처리도 하지않았을 때
CoroutineScope(Dispatchers.IO).launch {
launch(CoroutineName("child1")){
delay(1000)
Log.d("dayun","${this} end")
}.invokeOnCompletion {
Log.d("dayun","child1 throwable = $it")
}
launch(CoroutineName("child2")){
throw Exception("test")
}.invokeOnCompletion { throwable ->
Log.d("dayun","child2 throwable = $throwable")
}
delay(1000)
Log.d("dayun","${this} end")
}.invokeOnCompletion { throwable ->
Log.d("dayun","parent throwable = $throwable")
}
결과
child2 throwable = java.lang.Exception: test
child1 throwable = kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=StandaloneCoroutine{Cancelling}@1c5e461
우선 child 2에서 에러를 Throw를 했을 때 ParnetScope 가 Cancelling 되면서 Child 2도 같이 Cancel이 됩니다.
Parent의 invokeOnCompletion은 호출이 안되는데.. 왜 그런지 아시는 분은 댓글 좀
SupervisorJob을 이용했을 때
일단 SupervisorJob의 경우 자식 Scope의 예외가 Parent 로 전파되지 않게 하는 방법으로 알고있죠.
val supervisorJob = SupervisorJob()
CoroutineScope(Dispatchers.IO).launch {
launch(supervisorJob){
delay(1000)
Log.d("dayun","child1 end")
}.invokeOnCompletion {
Log.d("dayun","child1 throwable = $it")
}
launch(supervisorJob){
throw Exception("test")
}.invokeOnCompletion { throwable ->
Log.d("dayun","child2 throwable = $throwable")
}
delay(1000)
Log.d("dayun","parent end")
}.invokeOnCompletion { throwable ->
Log.d("dayun","parent throwable = $throwable")
}
결과
parent end
parent throwable = null
child1 end
child1 throwable = null
SupervisorJob 을 이용하면 child2 에서 난 예외가 parent 로 전파되지 않기 때문에 parent 도 성공적으로 end 되고 child1 도 성공적으로 end 됩니다.
그냥 Job 을 이용했을 때
val job = Job()
CoroutineScope(Dispatchers.IO).launch {
launch(job){
delay(1000)
Log.d("dayun","child1 end")
}.invokeOnCompletion {
Log.d("dayun","child1 throwable = $it")
}
launch(job){
throw Exception("test")
}.invokeOnCompletion { throwable ->
Log.d("dayun","child2 throwable = $throwable")
}
delay(1000)
Log.d("dayun","parent end")
}.invokeOnCompletion { throwable ->
Log.d("dayun","parent throwable = $throwable")
}
이렇게 되면 parent 의 coroutineContext 에 + job 이 된것이 child1 과 child2 의 parentScopeContext 가 되면서 외곽에 있는 CoroutineScope의 겨우 내부 Coroutine 의 예외를 전달받지 않습니다.
그러나, 같은 parent를 둔 child1 과 child2 의 경우 예외가 전달되어 cancel 이 됩니다.
결과
child1 throwable = kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelling}@48cbc8
parent end
parent throwable = null
매번 newInstance 로 Job 을 만든다면?
CoroutineScope(Dispatchers.IO).launch {
launch(Job()){
delay(1000)
Log.d("dayun","child1 end")
}.invokeOnCompletion {
Log.d("dayun","child1 throwable = $it")
}
launch(Job()){
throw Exception("test")
}.invokeOnCompletion { throwable ->
Log.d("dayun","child2 throwable = $throwable")
}
delay(1000)
Log.d("dayun","parent end")
}.invokeOnCompletion { throwable ->
Log.d("dayun","parent throwable = $throwable")
}
이렇게 되면 3개의 쓰레드가 각각이 별도의 coroutineContext 를 launch 하는 형태라 서로 예외를 전파시키지 않습니다.
이 코드에서는 결과적으로 supervisorJob 과 같은 결과를 볼 수 있습니다.
이 방법과 supervisorJob 의 경우 다른 Need 를 갖고 있다고 생각합니다.
new Job()으로 매번 context 를 넣어주는 행위는 새로운 문맥을 만드는 독자적인 행위로 보여집니다.
그러나 supervisorJob의 경우 개발자가 해당 코드를 보았을 때 에러전파가 상위로 전달되지 않게한다는 Need를 코드를 보고 이해할 수 있다고 생각합니다.
따라서 가독성을 높이기 위해서는 각 상황에 맞게 사용하는 것이 좋아보입니다.
Main Thread 에서 SupervisorJob 을 테스트할 때
val supervisorJob = SupervisorJob()
CoroutineScope(Dispatchers.Main).launch {
Log.d("dayun","start")
launch(supervisorJob){
delay(1000)
Log.d("dayun","child1 end")
}.invokeOnCompletion {
Log.d("dayun","child1 throwable = $it")
}
launch(supervisorJob){
throw Exception("test")
}.invokeOnCompletion { throwable ->
Log.d("dayun","child2 throwable = $throwable")
}
delay(1000)
Log.d("dayun","parent end")
}.invokeOnCompletion { throwable ->
Log.d("dayun","parent throwable = $throwable")
}
MainThread의 경우 싱글 스레드라 그런지 SupervisorJob 이 예상처럼 동작하지 않고 에러가 전파 되는 것으로 확인하였습니다.
mainThread에서 예외전파를 하지않으려면 try-catch 밖에 없을까요? 많은 제보 부탁드립니다.
'코틀린' 카테고리의 다른 글
코틀린 컬렉션 프레임워크 유용 함수들 (0) | 2024.05.06 |
---|---|
코틀린 Flows의 소개 (0) | 2022.04.12 |
보기 좋은 방법으로 Multiple Coroutine 기다리기 (0) | 2022.02.25 |
inline, noinline, crossinline (0) | 2022.02.11 |
코루틴 컨텍스트 전환 (0) | 2021.11.24 |