[Coroutine] ์ฝ”๋ฃจํ‹ด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

 ๐Ÿ’ก ์ฝ”๋ฃจํ‹ด์˜ ์ž‘๋™ ์›๋ฆฌ ์ค‘ ์•„์ฃผ ์ค‘์š”ํ•œ ๊ธฐ๋Šฅ์ธ ์ฝ”๋ฃจํ‹ด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•˜์—ฌ ํ•™์Šตํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์˜ˆ์™ธ ์ฒ˜๋ฆฌ

  • ์žกํžˆ์ง€ ์•Š๋Š” ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ์ฝ”๋ฃจํ‹ด๋„ ์žกํžˆ์ง€ ์•Š์€ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
  • ํฐ ์ฐจ์ด๋Š” ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋Š” ๋ถ€๋ชจ๋„ ์ข…๋ฃŒ์‹œํ‚ค๋ฉฐ, ์ทจ์†Œ๋œ ๋ถ€๋ชจ๋Š” ์ž์‹๋“ค ๋ชจ๋‘๋ฅผ ์ทจ์†Œ์‹œํ‚ต๋‹ˆ๋‹ค.

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

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

        launch {
            delay(500)
            println("Will be printed")
        }
    }

    launch {
        delay(2000)
        println("Will not be printed")
    }
}
// Will be printed
// Exception in thread "main" java.lang.Error: Some error...

  • ์˜ˆ์™ธ๋Š” ์ž์‹์—์„œ ๋ถ€๋ชจ๋กœ ์ „ํŒŒ๋˜๋ฉฐ, ๋ถ€๋ชจ๊ฐ€ ์ทจ์†Œ๋˜๋ฉด ์ž์‹๋„ ์ทจ์†Œ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์Œ๋ฐฉ์œผ๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ ์ „ํŒŒ๊ฐ€ ์ •์ง€๋˜์ง€ ์•Š์œผ๋ฉด, ๊ณ„ํ†ต ๊ตฌ์กฐ์ƒ ๋ชจ๋“  ์ฝ”๋ฃจํ‹ด์ด ์ทจ์†Œ๋˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ์ข…๋ฃŒ ๋ฉˆ์ถ”๊ธฐ

  • ์ฝ”๋ฃจํ‹ด ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ์€ ์žก์„ ํ†ตํ•ด์„œ ์ผ์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์—, ์ฝ”๋ฃจํ‹ด ๋นŒ๋” ๋‚ด๋ถ€์—์„œ ์ƒˆ๋กœ์šด ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋ฅผ try-catch ๋ฌธ์„ ํ†ตํ•ด ๋ž˜ํ•‘ํ•˜๋Š” ๊ฑด ์ „ํ˜€ ๋„์›€์ด ๋˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ์˜ˆ์™ธ ์ „ํŒŒ๋ฅผ ๋ฉˆ์ถ”๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•˜์—ฌ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

SupervisorJob

  • supervisorJob์„ ์‚ฌ์šฉํ•˜๋ฉด, ์ž์‹์—์„œ ๋ฐœ์ƒํ•œ ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ถ€๋ชจ ํ˜น์€ ๋‹ค๋ฅธ ์ž์‹๋“ค์—๊ฒŒ ์ž์‹ ์˜ ์˜ˆ์™ธ๊ฐ€ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
val scope = CoroutineScope(SupervisorJob())
scope.launch {
    // child1
}
scope.launch {
    // child2
}
  • ์œ„ ์ฝ”๋“œ์—์„œ child1์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ child2๋กœ ์˜ˆ์™ธ๊ฐ€ ์ „ํŒŒ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • SupervisorJob์„ ์‚ฌ์šฉํ•  ๋•Œ ๊ฐ€์žฅ ํ”ํ•œ ์‹ค์ˆ˜๋Š” ๋ถ€๋ชจ ์ฝ”๋ฃจํ‹ด์ด ์—†์ด SupervisorJob์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
fun main(): Unit = runBlocking {
    // ๋ถ€๋ชจ ์ฝ”๋ฃจํ‹ด์ด ์—†๋Š” ์žก์€ ์ผ๋ฐ˜ ์žก๊ณผ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™
    launch(SupervisorJob()) {
        launch {
            throw Error("error")
        }
    }
}
// Exception
  • ํ•˜๋‚˜์˜ ์ฝ”๋ฃจํ‹ด์ด ์ทจ์†Œ๋˜์–ด๋„ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์ด ์ทจ์†Œ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ํŠน์ง•์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ™์€ ์žก์„ ๋‹ค์ˆ˜์˜ ์ฝ”๋ฃจํ‹ด์—์„œ ์ปจํ…์ŠคํŠธ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข€ ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
val job = SupervisorJob()
launch(job) {
    throw Error("Error1")
}
launch(job) {
    throw Error("Error2")
}

supervisorScope

  • ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋ฅผ supervisorScope๋กœ ๋ž˜ํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด ๋ฐฉ์‹์€ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๋ถ€๋ชจ์™€์˜ ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
// in runBlocking
supervisorScope {
    launch {
        throw Error("error")
    }
    launch {
        throw Error("error")
    }
}
  • ์ด๋Š” ๋‹จ์ง€ ์ค‘๋‹จ ํ•จ์ˆ˜์ผ ๋ฟ์ด๋ฉฐ, ์ค‘๋‹จ ํ•จ์ˆ˜ ๋ณธ์ฒด๋ฅผ ๋ž˜ํ•‘ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์ผ๋ฐ˜์ ์œผ๋กœ ์„œ๋กœ ๋ฌด๊ด€ํ•œ ๋‹ค์ˆ˜์˜ ์ž‘์—…์„ ์Šค์ฝ”ํ”„ ๋‚ด์—์„œ ์‹คํ–‰ํ•  ๋•Œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
suspend fun notifyAnalytics(actions: List<UserAction>) =
    supervisorScope {
        actions.forEach { action ->
            launch {
                notifyAnalytics(action)
            }
        }
    }
  • ์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ์ ์€ supervisorScope๋Š” withContext(SupervisorJob())์œผ๋กœ ๋Œ€์ฒด๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
// ์ž˜๋ชป ๋œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค !
suspend fun notifyAnalytics(actions: List<UserAction>) = 
		withContext(SupervisorJob()) {}
  • ์œ„ ์ฝ”๋“œ๋Š” Job์ด ์ƒ์†๋˜์ง€ ์•Š๋Š” ์œ ์ผํ•œ ์ปจํ…์ŠคํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • ์ฝ”๋ฃจํ‹ด์€ ๊ฐ์ž ์ž์‹ ๋งŒ์˜ ์žก์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์žก์„ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์— ์ „๋‹ฌํ•˜์—ฌ ๋ถ€๋ชจ ๊ด€๊ณ„๋ฅผ ๋งบ์Šต๋‹ˆ๋‹ค.
  • ์œ„ ์ฝ”๋“œ์—์„œ๋Š” ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ withContext ์ฝ”๋ฃจํ‹ด์œผ๋กœ ์ „๋‹ฌ๋˜๋ฉฐ, ๋ถ€๋ชจ์™€ ์ž์‹ ์ฝ”๋ฃจํ‹ด์ด ๋ชจ๋‘ ์ทจ์†Œ๋˜์–ด ๋งˆ์ง€๋ง‰์œผ๋กœ ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ง‘๋‹ˆ๋‹ค.
  • ์ด๋Š” SupervisorJob์„ ๋ถ€๋ชจ๋กœ ๋ฐ”๊พธ์–ด๋„ ์›ํ•˜๋Š” ํšจ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

await

  • ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ async ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋Š” launch์ฒ˜๋Ÿผ ๋ถ€๋ชจ ์ฝ”๋ฃจํ‹ด์„ ์ข…๋ฃŒํ•˜๊ณ  ๋ถ€๋ชจ์™€ ๊ด€๋ จ์žˆ๋Š” ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋„ ์ข…๋ฃŒ์‹œํ‚ต๋‹ˆ๋‹ค.
suspend fun main() = supervisorScope {
    val str1 = async<String> {
        throw Exception()
    }
    val str2 = async {
        "Text2"
    }
    try {
        println(str1.await())
    } catch( e: Exception) {
        println(e)
    }

    println(str2.await())
}
// Exception
// Text2
  • ์ฝ”๋ฃจํ‹ด์ด ์˜ˆ์™ธ๋กœ ์ข…๋ฃŒ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜ํ™˜ํ•  ๊ฐ’์ด ์—†์ง€๋งŒ, await๊ฐ€ Exception์„ ๋˜์ ธ์„œ Exception์ด ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.
  • supervisorScope๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋˜ ๋‹ค๋ฅธ async๋Š” ์ค‘๋‹จ๋˜์ง€ ์•Š๊ณ  ๋๊นŒ์ง€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

CancellationException

  • ์˜ˆ์™ธ๊ฐ€ CancellationException์˜ ์„œ๋ธŒํด๋ž˜์Šค๋ผ๋ฉด ๋ถ€๋ชจ๋กœ ์ „ํŒŒ๋˜์ง€ ์•Š๊ณ , ํ˜„์žฌ ์ฝ”๋ฃจํ‹ด ์ทจ์†Œ๋งŒ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • CancellationException์€ ์—ด๋ฆฐ ํด๋ž˜์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ํด๋ž˜์Šค๋‚˜ ๊ฐ์ฒด๋กœ ํ™•์žฅ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
object MyException : CancellationException()

suspend fun main(): Unit = coroutineScope {
    launch { // 1 coroutine
        launch { // 2 coroutine
            delay(2000)
            println("not be printed")
        }
        throw MyException
    }
    launch { // 3 coroutine
        delay(2000)
        println("be printed")
    }
}
// 2์ดˆ ํ›„..
// be printed
  • ๋‘ ๊ฐœ์˜ ์ฝ”๋ฃจํ‹ด์ด 1, 3 ๋นŒ๋”๋กœ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.
  • 1์—์„œ CancellationException์˜ ์˜ˆ์™ธ์ธ MyException๋ฅผ ๋˜์ง€๋ฏ€๋กœ, ์˜ˆ์™ธ๋Š” 1์—์„œ ์‹œ์ž‘๋œ launch์—์„œ ์žกํžˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • 1์—์„œ ์‹œ์ž‘๋œ ์ฝ”๋ฃจํ‹ด์€ ์ž๊ธฐ ์ž์‹ ๊ณผ ์ž์‹์ธ 2 ๋นŒ๋”๋ฅผ ์ทจ์†Œํ•˜๊ณ , 3 ๋นŒ๋”๋Š” ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๊ณ  ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ

  • ์˜ˆ์™ธ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋ณธ ํ–‰๋™์„ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ฝ”๋ฃจํ‹ด์—์„œ๋Š” ์ด๋Ÿฐ ๊ฒฝ์šฐ์— CoroutineExceptionHandler ์ปจํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์˜ˆ์™ธ ์ „ํŒŒ๋ฅผ ์ค‘๋‹จ์‹œํ‚ค์ง€ ์•Š๊ณ  ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ํ•ด์•ผํ•  ๊ฒƒ๋“ค์„ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
fun main(): Unit = runBlocking {
    val handler =
        CoroutineExceptionHandler { ctx, exception ->
            println("Caught $exception")
        }
    val scope = CoroutineScope(SupervisorJob() + handler)
    scope.launch {
        delay(1000)
        throw Error("Error")
    }

    delay(3000)
}
// Caught java.lang.Error: Error

์ฐธ๊ณ 

https://tak8997.github.io/2021/04/17/Kotlin-Coroutine-Exception-Handling/