코루틴은 부모-자식 관계이므로 자식 코루틴이 예외가 발생하면되면 부모 코루틴도 예외가 발생한다.
이러한 해결책을 try-catch 가 아닌 다른 방법을 사용해야 한다. 코루틴의 상호작용으로 인한 예외이므로
try-catch는 아무 기능도 할 수 없다.
SupervisorJob
코루틴 종료를 멈추는 방법은 SupervisorJob를 사용하는 것이다.
SupervisorJob를 사용하면 자식에서 발생한 모든 예외를 무시할 수 있다.
fun main() {
runBlocking {
val scope = CoroutineScope(SupervisorJob())
scope.launch {
delay(1000)
throw Exception("error")
}
scope.launch() {
delay(2000)
println("will be printed")
}
delay(3000)
}
}
// result
Exception in thread "main" java.lang.Exception: error
will be printed
흔히하는 실수 중 하나는, SupervisorJob을 부모의 인자로 사용하는 것 이다.
SupervisorJob은 오직 하나의 자식만 가지기 때문에 예외를 처리하는데 도움이 되질 않는다.
fun main() {
runBlocking {
launch(SupervisorJob()) {
launch {
delay(1000)
throw Exception("error")
}
launch {
delay(2000)
println("will be printed")
}
}
delay(3000)
}
}
//result
Exception in thread "main" java.lang.Exception: error
하나의 코루틴이 다른 코루틴이 취소되지 않는 다는 점에서 다수의 코루틴에서 Context로 사용하는 방법이 더 나은 방법 이다.
fun main() = runBlocking {
val job = SupervisorJob()
launch(job) {
delay(1000)
throw Error(" error")
}
launch(job) {
delay(2000)
println("print")
}
job.join()
}
// result
Exception in thread "main" java.lang.Error: error
print
SupervisorScope
예외 전파를 막는 또 다른 방법은 CoroutineBuiilder를 SupervisorScope로 래핑하는 것 이다.
다른 코루틴에서 발생한 예외를 무시하고 부모와의 연결을 유지할 수 있다.
이는 CoroutineScope와 동일한 역할을 하지만 SupervisorJob을 상속한다고 이해하면 된다.
supervisorScope
public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = SupervisorCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
fun main(): Unit = runBlocking {
supervisorScope {
launch {
delay(1000)
throw Error(" error")
}
launch {
delay(2000)
println("print")
}
}
delay(1000)
println("Done")
}
// result
Exception in thread "main" java.lang.Error: error
print
Done
Await
fun main(): Unit = runBlocking {
supervisorScope {
val str1 = async<String> {
delay(1000)
throw Throwable("error")
}
val str2 = async {
delay(2000)
"Text2"
}
try {
println(str1.await())
} catch (e: Throwable) {
println(e)
}
println(str2.await())
}
}
// result
java.lang.Throwable: error
Text2
await 은 Launch와 동일한 작동을 하지만 에러를 부모 및 자식에게 전달하는 타이밍이 다르다.
await는 supervisorScope 가 사용되었기 때문에 async는 중단되지 않고 끝까지 실행 된다.
CancellationException은 부모까지 전파되지 않는다.
예외가 CancellationException의 서브클래스라면 부모로 전파되지 않고, 현재 코루틴을 취소시킬 뿐이다.
public actual typealias CancellationException = java.util.concurrent.CancellationException
fun main(): Unit = runBlocking {
coroutineScope {
launch { //1
launch { //2
delay(2000)
println("print")
}
throw CancellationException() //3
}
launch { //4
delay(2000)
println("print")
}
}
}
//2초뒤
print
위 코드에서 두개의 코루틴 빌더에서 1과 4가 시작되고, 3에서 CancellationException 로 예외를 던진다.
이 예외는 1에서 시작되는 lauch 에서 잡히게 되며 1과 2 의 코루틴을 취소한다.
4에서 시작된 코루틴 은 영향받지 않고 정상적으로 출력할 수 있다.
코루틴 예외 핸들러
예외를 다룰 때 예외가 나타날 시 기본 로직에 대해 정의하고 싶을 때가 있다.
이럴 경우 CoroutineExceptionHandler Context를 사용할 수있다.
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { coroutineContext, throwable ->
println("error : $throwable")
}
val scope = CoroutineScope(SupervisorJob() + handler)
scope.launch {
delay(1000L)
throw Error("Error")
}
scope.launch {
delay(2000)
println("print")
}
delay(3000)
}
// result
error : java.lang.Error: Error
print
'도서 > 코틀린 코루틴' 카테고리의 다른 글
2장 코루틴 라이브러리 - 디스패쳐 (0) | 2024.02.09 |
---|---|
2장 코루틴 라이브러리 - 코루틴 스코프 함수 (1) | 2024.02.04 |
2장 코루틴 라이브러리 - Job과 자식 코루틴 기다리기 (0) | 2024.02.03 |
2장 코루틴 라이브러리 - Cancel (0) | 2024.02.03 |
2장 코루틴 라이브러리 - CoroutineContext (1) | 2024.02.03 |