코루틴 스코프를 사용할때, viewmodelScope, lifecycleScope등 생명주기에 따라가는 코루틴을 사용하기 위한 스코프 인것 외에 더 활용하는 방법을 모른다.
이번에 Flow를 사용하며 코루틴 스코프에 대한 이해도를 키우고 비동기 처리를 더 확실하고 효율적으로 하기 위해 스코프를 좀 더 공부해봐야 겠다는 생각이 들어 정리해본다.
fun main() = runBlocking<Unit> {
val job = launch(Dispatchers.Default) {
repeat(10) { index ->
if (isActive) {
println("operation number $index")
Thread.sleep(100)
} else {
// perform some cleanup on cancellation
withContext(NonCancellable) {
delay(100)
println("Clean up done!")
}
throw CancellationException()
}
}
}
delay(250)
println("Cancelling Coroutine")
job.cancel()
val globalCoroutineJob = GlobalScope.launch {
repeat(10) {
println("$it")
delay(100)
}
}
delay(250)
globalCoroutineJob.cancel()
delay(1000)
}
다음 함수를 실행 해보자
operation number 0
operation number 1
operation number 2
Cancelling Coroutine
0
1
Clean up done!
2
Process finished with exit code 0
job을 캔슬해서 코루틴 스코프를 취소했는대 println("Clean up done!")이 수행되는걸 볼수 있다.
withContext에 NonCancellable를 넣었기 때문인대, 이 때문에 취소되지 않는 컨텍스트에서 코드를 실행되어 작업을 수행할 수 있다.
코루틴을 선언하고 지연해서 수행할 수 있다.
launch에 CoroutineStart.LAZY를 넣어주면 되는대,
이때 suspend함수나 .start()가 선언 되야 스코프가 수행된다.
fun main() = runBlocking<Unit> {
val job = launch(start = CoroutineStart.LAZY) {
networkRequest()
println("result received")
}
delay(200)
job.start()
println("end of runBlocking")
}
suspend fun networkRequest(): String {
delay(500)
return "Result"
}
이 함수를 실행하면 end of runBlocking 런블로킹이 끝난뒤 job이 수행되는걸 볼 수 있다.
end of runBlocking
result received
fun main() {
val scope = CoroutineScope(Job())
try {
scope.launch {
functionThatThrowsIt()
}
} catch (e: Exception) {
println("Caught: $e")
}
Thread.sleep(100)
}
fun functionThatThrowsIt() {
throw RuntimeException()
}
여기서 발생한 에러는 try-catch에서 포착하지 못한다.
스코프 바깥에 배치되어 있기 때문에 그렇다.
fun main() = runBlocking<Unit>() {
try {
doSomeThingSuspend()
} catch (e: Exception) {
println("Caught $e")
}
}
private suspend fun doSomeThingSuspend() {
coroutineScope {
launch {
throw RuntimeException()
}
}
}
하지만 다음과 같은 경우 runBlocking 코루틴 스코프 내에서 내부 coroutineScope가 에러가 나면 try-catch로 포착할 수 있다.
(runBlocking이 아닌 CoroutineScope(Job())인 경우 에러가 발생즉시 스코프가 종료되기때문에 포착이 안된다.)
이는 코루틴 스코프의 특성과 연관되어 있다.
상위 코루틴 스코프로 감싸져 있으면 자식 코루틴 스코프에서 발생하는 예외까지 같이 잡아내기 때문
코루틴 스코프 내의 에러들을 잡아주는 핸들러로 CoroutineExceptionHandler가 있다.
fun main() {
val exceptionHandler = CoroutineExceptionHandler { context, exception ->
println("Caught $exception in CoroutineExceptionHandler")
}
val scope = CoroutineScope(Job())
scope.launch {
launch(exceptionHandler) {
functionThatThrowsIt()
}
}
Thread.sleep(100)
}
핸들러의 context는 스코프, exception은 발생한 에러다.
이렇게 선언하면 하위 스코프의 에러까지 전부 잡아준다.
val scope = CoroutineScope(Job() + exceptionHandler) 이렇게 Job이랑 합쳐서 넣어줄수도 있고
viewmodelScope.launch(ech) 뷰모델 스코프에 넣어줄수도 있다.
예외처리방법
supervisorScope와 SupervisorJob 사용: 자식 코루틴의 예외가 부모 코루틴으로 전파되지 않도록 할 수 있다. 이를 통해 부분적인 실패를 허용할 수 있다
withContext 사용: withContext 블록 내에서 발생한 예외를 포착할 수 있다
CoroutineExceptionHandler 사용: 전역 예외 처리기를 등록하여 예외를 처리할 수 있다.
https://kotlinlang.org/docs/exception-handling.html#supervision-scope