1. 예외 전파
코루틴에서 예외는 기본적으로 전파된다. 코루틴 내에서 발생한 예외는 해당 코루틴을 취소시키고 부모 코루틴이나 상위 스코프에 전달 된다.
fun main() = runBlocking {
launch {
throw Exception("Something went wrong")
}
launch {
println("test")
}
}
// 출력
Exception in thread "main" java.lang.Exception: Something went wrong
at section8.Code8_7Kt$main$1$1.invokeSuspend(code8-7.kt:7)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
...
위 코드에서 Exception이 발생하면 job이 종료되고 부모 코루틴도 영향을 받아 test를 출력을 하지 못하게 된다.
2. 예외 전파 제한
- 코루틴의 구조화를 꺤다
- 구조화된 동시성을 무시하고 코루틴 빌더를 별도로 실행하여 부모-자식 관계를 끊어 예외가 전파되지 않도록 할 수 있다.
-
fun main() = runBlocking { CoroutineScope(Dispatchers.Default).launch { throw Exception("This exception will not affect the parent") } println("Parent continues") } // 출력 Parent continues
- 구조화가 깨져 상위 코루틴에 영향이 없는걸 볼 수 있다
- SupervisorJob을 사용한다
- SupervisorJob을 사용하면 자식 코루틴에서 발생한 예외가 부모 코루틴으로 전파되지 않는다.
-
fun main() = runBlocking { val supervisor = SupervisorJob() val scope = CoroutineScope(supervisor + Dispatchers.Default) val job1 = scope.launch { throw Exception("Job1 failed") } val job2 = scope.launch { println("Job2 is running") } joinAll(job1, job2) // Job2는 영향을 받지 않습니다. }
launch(SupervisorJob()) 이러한 방식으로 SupervisorJob 설정을 하게되면 안된다.
새로운 코루틴 빌더(launch, async 등)는 Job? = parent[Job]를 기반으로 내부적으로“자식 Job”을 생성하기 때문에
부모의 Job이 있다면 그 Job의 자식이 되고 부모가 SupervisorJob이면 자식도 supervisor 성격을 갖게 된다.
결국 launch(SupervisorJob())방식으로 코루틴을 만들어도 부모가 Job이면 SupervisorJob의 역할을 제대로 못하게 된다
- supervisorScope를 사용한다
- supervisorScope 함수는 SupervisorJob 객체를 가진 CoroutineScope 객체를 생성한다.
- supervisorScope 함수를 통해 생성된 SupervisorJob 객체는 supervisorScope 함수를 호출한 코루틴을 부모로 가진다.
- supervisorScope 함수를 통해 생성된 SupervisorJob 객체는 코드가 모두 실행되고 자식 코루틴의 실행도 완료되면 자동으로 완료된다.
fun main() = runBlocking {
supervisorScope {
launch {
throw Exception("Child failed")
}
launch {
println("Sibling is unaffected")
}
}
}
3. CoroutineExceptionHandler 예외처리 핸들러
CoroutineExceptionHandler는 코루틴의 예외를 처리할 수 있는 글로벌 핸들러를 한다. launch에서만 동작 가능하다.
CoroutineExceptionHandler는 부모 코루틴이나 최상위 코루틴에 설정되어야 자식 코루틴에서 발생한 예외를 처리할 수 있다.
fun main() = runBlocking<Unit> {
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
println("예외 발생 ${throwable}")
}
CoroutineScope(Dispatchers.IO).launch(CoroutineName(name = "Coroutine1")) {
launch(CoroutineName(name = "Coroutine2") + exceptionHandler) {
throw Exception("Coroutine2에 예외가 발생했습니다")
}
}
delay(timeMillis = 1000L)
}
위 코드 같은경우 exceptionHandler가 설정된거 같지만 에러가난 코루틴에서는 에러를 부모에게 던지는 행위만 하기때문에 에러 핸들러가 작동하지 않는다. Coroutine1에 exceptionHandler을 설정해야지 정상적으로 동작할 수 있다.
4. try-catch문을 사용한 예외 처리
코루틴 내에서 로컬한 예외 처리를 가능하게 하려면 특정 블록에 try-catch를 사용할 수 있다.
fun main() = runBlocking {
val job = launch {
try {
throw Exception("Try-catch example")
} catch (e: Exception) {
println("Caught: ${e.message}")
}
}
job.join()
}
// 출력
Caught: Try-catch example
5. async 예외 처리
- async 내부에서 에러를 잡는 경우
async 블록 내부에서 try-catch를 사용하여 에러를 처리하면 에러가 더 이상 Deferred 객체로 전달되지 않는다. await에서 에러가 발생하지 않고 정상적으로 결과를 반환하거나 Deferred가 처리 완료 상태가 된다.
val deferred = async {
try {
throw Exception("Something went wrong!")
} catch (e: Exception) {
println("Caught exception in async: ${e.message}")
}
}
// 에러는 이미 async 내부에서 처리되었기 때문에 await에서 에러가 발생하지 않음
deferred.await() // null 반환 (예제에서는 명시적으로 값이 반환되지 않았기 때문에)
- await에서 에러를 잡는 경우
async 내부에서 에러를 잡지 않고 그대로 두면 해당 에러는 Deferred 객체에 저장된다. 이후 await를 호출할 때 에러가 발생한다.
val deferred = async {
throw Exception("Something went wrong!")
}
try {
deferred.await() // 여기서 에러 발생
} catch (e: Exception) {
println("Caught exception in await: ${e.message}")
}
코루틴은 기본적으로 부모-자식 관계를 형성하며 모든 자식 코루틴의 Job은 부모 코루틴의 Job에 연결되어있다.
자식 코루틴에서 예외가 발생하면 부모 Job은 이 예외를 감지하여 전체 코루틴 취소하게 된다.
try-catch로 예외를 잡아도 해당 코루틴의 Job은 취소 상태로 설정되서 취소 상태는 부모 Job에 전파되므로 부모 코루틴과 다른 자식 코루틴이 영향을 받아 예외전파로 인해 다른 코루틴이 멈추게 된다.
이를 방지하기위해 supervisorScope을 사용해 예외 전파를 막을 수 있다.
fun main() = runBlocking {
supervisorScope {
val deferred = async {
throw Exception("Something went wrong!")
}
try {
deferred.await() // 여기서 예외 발생
} catch (e: Exception) {
println("Caught exception in await: ${e.message}")
}
}
println("Parent coroutine is not cancelled.")
}
'개념 저장소 > coroutine' 카테고리의 다른 글
공유 자원 관리 (1) | 2025.01.01 |
---|---|
suspend (0) | 2024.12.27 |
구조화된 동시성 (2) | 2024.12.21 |
Coroutine Context (1) | 2024.12.12 |
Deferred, async/await, withContext (0) | 2024.12.09 |