관리 메뉴

오늘도 더 나은 코드를 작성하였습니까?

10장 예외처리 본문

Coroutine/코틀린 코루틴

10장 예외처리

hik14 2026. 2. 9. 00:10

잡히지 않은 예외가 발생하면, 프로그램이 종료하듯 코루틴도 잡히지 않은 예외가 발생했을때 종료합니다.

코루틴 빌더는 부모도 종료시키며, 취소된 부모는 자식들 모두를 취소시킨다.

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking


fun main(): Unit = runBlocking {
    launch {
        launch {
            delay(1000)
            throw Error("some error")
        }
        launch {
            delay(2000)
            println("Will not be printed")
        }
        launch {
            delay(500) // 예외 발생보다 빠름
            println("Will be printed")
        }
    }
    launch {
        delay(2000)
        println("Will not be printed")
    }
}

예외 전파가 정지 되지 않는 이상 계통 구조상 모든 코루틴이 취소된다

코루틴 종료 멈추기

코루틴 간의 상호작용은 잡을 통해서 일어나기 때문에 코루틴 빌더 내부에서 새로운 코루틴 빌더를 try-catch문을 통해 래핑 하는건  전혀 도움이 되지 못합니다.

import kotlinx.coroutines.*


fun main(): Unit = runBlocking {
    try {
        launch {
            delay(1000)
            throw Error("some error")
        }
    } catch (e: Throwable) {
        println("Will not be printed")
    }
    launch {
        delay(2000)
        throw Error("some error")
    }
}

 

SupervisorJob

코루틴 종료를 멈추는 가장 중요한 방법은 supervisorJob을 사용하는 것입니다.

supervisorJob은 자식에서 발생한 모든 예외를 무시할수 있다.

 

SupervisorJob 다수의 코루틴을 시작하는 스코프로 사용한다.

import kotlinx.coroutines.*


fun main(): Unit = runBlocking {
    val scope = CoroutineScope(SupervisorJob())
    scope.launch {
        delay(1000L)
        throw Error("Some error")
    }

    scope.launch {
        delay(2000L)
        println("Will be printed")
    }

    delay(3000L)
}

//Exception in thread "DefaultDispatcher-worker-
//Will be printed

 

* 흔한 실수 중 하나는 SupervisorJob을 부모 코루틴의 인자로 사용하는것.

SupervisorJob은 단 하나의 자식만 가지기 때문에 예외를 처리하는데 아무런 도움이 되지 않습니다.

import kotlinx.coroutines.*


fun main(): Unit = runBlocking {
    // 이렇게 하지마세요. 자식 코루틴 하나가 있고,
    // 부모 코루틴이 없는 잡은 일반 잡과 동일하게 작동합니다.
    launch(SupervisorJob()) {
        launch {
            delay(1000L)
            throw Error("Some error")
        }

        launch {
            delay(2000L)
            println("Will be not printed")
        }
    }

    delay(3000L)
}

 

1. launch(SupervisorJob())의 함정: 부모-자식 관계 단절

launch의 인자로 SupervisorJob()을 넘기면, 새로 생성된 코루틴은 호출한 부모(runBlocking)와의 구조적 동시성(Structured Concurrency) 관계가 끊어집니다.

  • 관계 단절: SupervisorJob()은 기존 부모의 Job을 상속받는 게 아니라, **완전히 새로운 뿌리(Root)**가 됩니다.
  • 문제점: 부모(runBlocking)는 자식 코루틴이 끝날 때까지 기다려주지 않습니다. 만약 코드 끝에 delay(3000L)이 없었다면, 자식들이 실행되기도 전에 프로그램이 끝났을 겁니다.

2. "슈퍼바이저" 역할을 못 함

 

코루틴은 1인 1 Job 체제입니다

launch나 async 같은 코루틴 빌더는 실행될 때 무조건 자기 자신만의 새로운 Job을 생성합니다.

 

  • 전달한 SupervisorJob(): 새로 만들어질 코루틴의 부모 역할을 합니다.
  • 새로 생성된 launch: 부모가 누구든 상관없이, 자기 자신을 제어할 새로운 Job을 만듭니다. (이 녀석은 SupervisorJob이 아니라 일반 Job입니다.)

 

코루틴의 세계관에서 **"부모가 일반 Job이면, 자식의 에러를 견디지 못한다"**는 철칙이 있습니다.

  • 손자 (B): 에러 발생! -> "아빠(A)한테 알리자!"
  • 자식 (A): (자기만의 일반 Job을 가지고 있음) -> "어이쿠, 내 자식이 죽었네? 나도 일반 Job이니까 규칙대로 나 자신을 취소하고, 내 다른 자식들(손자 C)도 다 죽여야겠다!"
  • SupervisorJob (최상위 부모): (자식 A로부터 에러 보고를 받음) -> "음, 나는 슈퍼바이저니까 자식 A가 죽어도 나는 안 죽어." (하지만 이미 자식 A와 그 식구들은 몰살된 상태)
import kotlinx.coroutines.*


fun main(): Unit = runBlocking {
    val job = SupervisorJob()
    launch(job) {
        launch(job)  {
            delay(1000L)
            throw Error("Some error")
        }

        launch(job) {
            delay(2000L)
            println("Will be printed")
        }
    }

    job.join()
}

//Exception in thread "main" java.lang
//Will be printed

job.join()의 함정

현재 main 함수의 마지막에 job.join()을 호출. SupervisorJob은 자식이 모두 끝나도 스스로 Completed 상태가 되지 않습니다.

* 즉, 이 프로그램은 "Will be printed"를 출력한 후에도 종료되지 않고 무한히 대기하게 됩니다. (사용자님이 수동으로 프로세스를 죽여야한다.

 

SupervisorScope

다른 코루틴에서 발생한 예외를 무시하고 부모와의 연결을 유지한다

import kotlinx.coroutines.*


fun main(): Unit = runBlocking {
    supervisorScope {
        launch {
            delay(1000)
            throw Error("Some error")
        }

        launch {
            delay(2000)
            println("Will be printed")
        }
    }
    delay(1000)
    println("Done")
}

//Exception in thread 
//Will be printed
//Done

 

supervisorScope는 단지 중단함수일 뿐, 다른 중단함수를 랩핑하는데 사용된다.

일반적인 사용법은 서로 무관한 다수의 작업을 스코프내에서 실행시키는 것

 

coroutineScope

코루틴 빌더와 달리 부모에 영향을 미치는 대신 try-catch 이용해 잡을수 있는 예외를 던진다.

 

*supervisorScope 는 withContext(Supervisorjob()){ ... }으로 대체될 수 없다.

job은 상속되지 않는 컨텍스트이며, 모든 코루틴은 각각의 job을 가진다. 즉 Supervisorjob는 withContext의 부모일뿐, withContext의 job은 일반 잡이기에 예외 발생시 취소되고, 다른 자식들도 모두 취소된다.

 

await

async도 launch 처럼 부모 코루틴을 종료하고, 부모와 관련있는 다른 코루틴 빌더도 종료시킨다.

SupervisorJob, supervisorScope를 사용하면 어떻게 될까?

import kotlinx.coroutines.*

class MyException : Throwable()

suspend fun main(): Unit = supervisorScope {
    val str1 = async<String> {
        delay(1000)
        throw MyException()
    }

    val str2 = async {
        delay(2000)
        "Text2"
    }

    try {
        println(str1.await())
    } catch (e: MyException) {
        println(e)
    }
    println(str2.await())
}

//MyException
//Text2

 

supervisorScope내에서 첫번째 str1에 MyException을 던져도 str2는 중단되지 않고 끝까지 실행한다.

CancellationException은 부모까지 전파되지 않는다.

cancellationException의 서브클래스라면 부모로 전파되지 않는다.

현재 코루을 취시킬뿐.

import kotlinx.coroutines.*

object MyNonPropagatingException : CancellationException()

suspend fun main(): Unit = coroutineScope {
    launch { //1
        launch { //2
            delay(2000)
            println("Will not be printed")
        }
        //3
        throw MyNonPropagatingException
    }
    launch {//4
        delay(2000)
        println("Will be printed")
    }
}

//Will be printed

 

4에서 시작한 코루틴의 예외의 영향을 받지 않고, 그대로 출력됨.

 

코루틴 예외 핸들러

예외를 처리하는 기본 행동을 정의하는것은 유용하다.

CoroutineExceptionHandler 컨텍스트를 사용한다.

import kotlinx.coroutines.*


fun main(): Unit = runBlocking {
    val handler = CoroutineExceptionHandler { ctx, exception ->
        println("Caught $exception")
    }

    val scope = CoroutineScope(SupervisorJob() + handler)
    scope.launch {
        delay(1000)
        throw Error("Some error")
    }

    scope.launch {
        delay(2000)
        println("Will be printed")
    }

    delay(3000)
}

//Caught java.lang.Error: Some error
//Will be printed

'Coroutine > 코틀린 코루틴' 카테고리의 다른 글

9장 취소  (0) 2026.02.01
8장 Job과 자식 코루틴 기다리기.  (0) 2026.01.26
7장 코루틴 컨텍스트  (1) 2026.01.19
6장 코루틴 빌더  (0) 2026.01.05
5장 코루틴 언어차원에서의 지원 vs 라이브러리  (0) 2025.12.22