[Coroutine] ์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ

๐Ÿ’ก ์ฝ”๋ฃจํ‹ด ๋นŒ๋”์—์„œ ํ™œ์šฉ๋˜๋Š” ์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ์— ๋Œ€ํ•˜์—ฌ ํ•™์Šตํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ

  • ์ฝ”๋ฃจํ‹ด ๋นŒ๋”์˜ ์ •์˜๋ฅผ ๋ณด๋ฉด ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ CoroutineContext์ž„์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job
  • ๋ฆฌ์‹œ๋ฒ„๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋งˆ์ง€๋ง‰ ์ธ์ž์˜ ๋ฆฌ์‹œ๋ฒ„๋„ CoroutineScope ํƒ€์ž…์ด๋ฉฐ, ์ค‘์š”ํ•œ ๊ฐœ๋…์œผ๋กœ ํ™œ์šฉ๋˜๋Š” CoroutineContext์˜ ์ •์˜๋ฅผ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค !

CoroutineContext ์ธํ„ฐํŽ˜์ด์Šค

  • CoroutineContext๋Š” ์›์†Œ๋‚˜ ์›์†Œ๋“ค์˜ ์ง‘ํ•ฉ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.
  • ์ปจํ…์ŠคํŠธ์˜ ์ง€์ •๊ณผ ๋ณ€๊ฒฝ์„ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด CoroutineContext์˜ ๋ชจ๋“  ์›์†Œ๋Š” CourtineContext๋กœ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋˜ํ•œ ์ปจํ…์ŠคํŠธ์—์„œ ๋ชจ๋“  ์›์†Œ๋Š” ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ Key๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

CoroutineContext์—์„œ ์›์†Œ ์ฐพ๊ธฐ

  • CoroutineContext๋Š” ์ปฌ๋ ‰์…˜๊ณผ ๊ฐœ๋…์ด ๋น„์Šทํ•˜๊ธฐ ๋•Œ๋ฌธ์— get์„ ์‚ฌ์šฉํ•œ ์œ ์ผํ•œ ํ‚ค๋ฅผ ๊ฐ€์ง„ ์›์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์›์†Œ๋ฅผ ์ฐพ์ง€ ๋ชปํ•œ๋‹ค๋ฉด Null์ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
fun main() {
    val ctx: CoroutineContext = CoroutineName("A name")

    val coroutineName: CoroutineName? = ctx[CoroutineName] //ctx.get(Job)
    val job: Job? = ctx[job]
    println(job) // null
}
  • CoroutineContext๋Š” ์ฝ”ํ‹€๋ฆฐ ์ฝ”ํˆฌํ‹ด์ด ์–ธ์–ด์ ์œผ๋กœ ์ง€์›ํ•˜๊ธฐ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— kotlin.coroutines์—์„œ import๋ฉ๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ Job์ด๋‚˜ CoroutineName๊ณผ ๊ฐ™์€ ์ปจํ…์ŠคํŠธ๋Š” kotlinx.coroutines ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ importํ•ฉ๋‹ˆ๋‹ค.

์ปจํ…์ŠคํŠธ ๋”ํ•˜๊ธฐ

  • CoroutineContext๋Š” ๋‘ ๊ฐœ์˜ CoroutineContext๋ฅผ ํ•ฉ์ณ ํ•˜๋‚˜์˜ CoroutineContext๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ํŠน์ง•์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹ค๋ฅธ ํ‚ค๋ฅผ ๊ฐ€์ง„ ๋‘ ์›์†Œ๋ฅผ ๋”ํ•˜๋ฉด ๋งŒ๋“ค์–ด์ง„ ์ปจํ…์ŠคํŠธ๋Š” ๋‘ ๊ฐ€์ง€ ํ‚ค๋ฅผ ๋ชจ๋‘ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
fun main() {
    val ctx1: CoroutineContext = CoroutineName("name1")
    println(ctx1[CoroutineName]?.name) //name1
    println(ctx1[Job]?.isActive) // null

    val ctx2: CoroutineContext = Job()
    println(ctx2[CoroutineName]?.name) // null
    println(ctx2[Job]?.isActive) // true 

    val ctx3 = ctx1 + ctx2
    println(ctx3[CoroutineName]?.name) // name1
    println(ctx2[Job]?.isActive) // true 
}

์ปจํ…์ŠคํŠธ ์›์†Œ ์ œ๊ฑฐ

  • minusKey ํ•จ์ˆ˜์— ํ‚ค๋ฅผ ๋„ฃ๋Š” ๋ฐฉ์‹์œผ๋กœ ์›์†Œ๋ฅผ ์ปจํ…์ŠคํŠธ์—์„œ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
fun main() {
    val ctx1: CoroutineContext = CoroutineName("name1") + Job()
    println(ctx1[CoroutineName]?.name) //name1
    println(ctx1[Job]?.isActive) // true

    val ctx2: ctx1.minusKey(CoroutineName)
    println(ctx2[CoroutineName]?.name) // null
    println(ctx2[Job]?.isActive) // true 

    val ctx3 = (ctx1 + CoroutineName("name2")).minusKey(CoroutineName)
    println(ctx3[CoroutineName]?.name) // null
    println(ctx2[Job]?.isActive) // true 
}

์ปจํ…์ŠคํŠธ ํด๋”ฉ

  • ์ปจํ…์ŠคํŠธ์˜ ๊ฐ ์›์†Œ๋ฅผ ์กฐ์ž‘ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋‹ค๋ฅธ ์ปฌ๋ ‰์…˜์˜ fold์™€ ์œ ์‚ฌํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

[fold]

  • ์ปฌ๋ ‰์…˜์„ ์ˆœํšŒํ•˜๋ฉด์„œ ๊ฐ ์›์†Œ๋ฅผ ๋ˆ„์ ํ•˜์—ฌ ํ•˜๋‚˜์˜ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ๊ณ ๊ณ„ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
  • ์ดˆ๊ธฐ๊ฐ’๊ณผ ๋ˆ„์  ํ•จ์ˆ˜๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„, ์ปฌ๋ ‰์…˜์˜ ๊ฐ ์›์†Œ์— ๋Œ€ํ•ด ๋ˆ„์  ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ–์—ฌ ์ตœ์ข… ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
fun main() {
    val numbers = listOf(1,2,3,4,5)
    val sum = numbers.fold(0) { acc, number ->
        acc + number
    }
    println(sum) // 15
}

inline fun <T, R> Iterable<T>.fold(
    initial: R,
    operation: (acc: R, T) -> R
): R
  • CoroutineContext์—์„œ fold๋ฅผ ์•„๋ž˜ ๊ทœ์น™์„ ํ†ตํ•ด ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋ˆ„์‚ฐ๊ธฐ์˜ ์ฒซ ๋ฒˆ์งธ ๊ฐ’์„ ๋„ฃ์–ด์คŒ
    • ๋ˆ„์‚ฐ๊ธฐ์˜ ํ˜„์žฌ ์ƒํƒœ์™€ ํ˜„์žฌ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š” ๋ˆ„์‚ฐ๊ธฐ์˜ ๋‹ค์Œ ์ƒํƒœ๋ฅผ ๊ณ„์‚ฐํ•  ์—ฐ์‚ฐ ์ง„ํ–‰
fun main() {
    val ctx1: CoroutineContext = CoroutineName("name1")

    ctx1.fold("") { acc, element -> "$acc$element" }
        .also(::println)
}

์ฝ”ํˆฌ๋ฆฐ ์ปจํ…์ŠคํŠธ์™€ ๋นŒ๋”

  • CoroutineContext๋Š” ์ฝ”๋ฃจํ‹ด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
  • ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„์˜ ์˜ํ–ฅ ์ค‘ ํ•˜๋‚˜๋กœ ๋ถ€๋ชจ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ปจํ…์ŠคํŠธ๋ฅผ ์ž์‹์—๊ฒŒ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  • ์ž์‹์€ ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์†๋ฐ›๋Š”๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
fun CoroutineScope.log(msg: String) {
    val name = coroutineContext[CoroutineName]?.name
    println("[$name] $msg")
}

fun main() = runBlocking(CorutineName("main")) {
    log("Started") //[main] Started
    val v1 = async {
        delay(500)
        log("Running async") // [main] Running async
        42
    }
    launch {
        delay(1000)
        log(Running launch") // [main] Running launch
    }
    log("the answer is ${v1.await()}")
    // [main] the answer is 42
}
  • ๋ฌผ๋ก  ๋ชจ๋“  ์ž์‹์€ ๋นŒ๋”์˜ ์ธ์ž์—์„œ ์ •์˜๋œ ํŠน์ • ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ์ƒ์†๋ฐ›์€ ์ปจํ…์ŠคํŠธ๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์•„๋ž˜์™€ ๊ฐ™์€ ๊ณต์‹์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
newContext = defaultContext + parentContext + childContext
  • defaultContext๋Š” ContinuationInterceptor๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” Dispathcers.Default์ด๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋””๋ฒ„๊ทธ ๋ชจ๋“œ์ผ ๋•Œ๋Š” CoroutineId๋„ ๋””ํดํŠธ๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค.
  • Job์€ ํ•ญ์ƒ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ์ฝ”๋ฃจํ‹ด์˜ ์ž์‹๊ณผ ๋ถ€๋ชจ๊ฐ€ ์†Œํ†ตํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ํŠน๋ณ„ํ•œ ์ปจํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

์ค‘๋‹จ ํ•จ์ˆ˜์—์„œ์˜ ์ปจํ…์ŠคํŠธ

  • CoroutineScope๋Š” ์ปจํ…์ŠคํŠธ๋ฅผ ์ ‘๊ทผํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” coroutineContext ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ ์ผ๋ฐ˜์ ์ธ ์ค‘๋‹จ ํ•จ์ˆ˜์—์„œ๋„ ์ปจํ…์ŠคํŠธ์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๊ฐœ๋ณ„์ ์œผ๋กœ ์ƒ์„ฑ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
// ์ค‘๋‹จ ํ•จ์ˆ˜์—์„œ ์ปจํ…์ŠคํŠธ ์ ‘๊ทผ
suspend fun printName() {
    println(coroutineContext[CoroutineName]?.name)
}

suspend fun main() = withContext(CoroutineName("Outer")){
    printName() // Outer
    launch(CoroutineName("Inner"){
        printName() // Inner
    }
            delay(10)
            printName() // Outer
}

// ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
class MyCustomContext: CoroutineContext.Element {
    override val key : CoroutineContext.Key<*> = Key

    companion object Key :
        CoroutineCotext.Key<MyCustomContext>
}
  • ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ๋งค์šฐ ์ ์ง€๋งŒ, ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ๊ณผ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ’์„ ์‰ฝ๊ฒŒ ์ฃผ์ž…ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ€๋” ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์š”์•ฝ

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