관리 메뉴

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

7장 코루틴 컨텍스트 본문

Coroutine/코틀린 코루틴

7장 코루틴 컨텍스트

hik14 2026. 1. 19. 01:01

 

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job { ... }

 

코루틴 빌더의 첫번째 인자는 CoroutineContext 이다.

마지막 인자인 block의 리시버 타입은 CoroutineScope이다.

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

 

CoroutineScope는 CoroutineContext의 랩퍼처럼 보인다.

public interface Continuation<in T> {
    public val context: CoroutineContext

    public fun resumeWith(result: Result<T>)
}

 

Continuation도 CoroutineContext를 포함하고 있다.

 

CoroutineContext는 무엇일까?

 

CoroutineContext 인터페이스

CoroutineContext는 원소나 원소들의 집합을 나타내는 인터페이스.

Job, CoroutineName, CoroutineDispatcher Element 객체들이 인덱싱된 집합 (map, set과 같은 컬렉션이랑 비슷한 개념)

Element 또한 CoroutineContext이다.

 

CoroutineContext의 모든 원소는 식별할 수 있는 유일한 Key를 가지고 있다.

fun main() {
    val name: CoroutineName = CoroutineName("A name")
    val element: CoroutineContext.Element = name
    val context: CoroutineContext = element
    
    val job: Job = Job()
    val jobElement: CoroutineContext.Element = job
    val jobContext: CoroutineContext = jobElement
}

 

CoroutineContext 에서 원소 찾기

CoroutineContext는 컬렉션과 비슷하기때문에 get을 이용해 유일한 키를 가진 원소를 찾으수 있다.

*해당 원소가 없으면, null을 반환한다.

fun main() {
    val ctx: CoroutineContext = CoroutineName("A name")
    
    val coroutineName: CoroutineName? = ctx[CoroutineName] // ctx.get(CoroutineName)
	println(coroutineName?.name) //A name
    
    val job: Job? = ctx[Job]
    println(job) //null
}

 

CoroutineName은 타입이나 클래스가 아닌 companion 객체이다.

public final data class CoroutineName public constructor(name: kotlin.String) : AbstractCoroutineContextElement {
    public companion object Key : CoroutineContext.Key<CoroutineName> {
    }
public interface Job : CoroutineContext.Element {
    public companion object Key : CoroutineContext.Key<Job> {
    }

 

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)        // Active 상태라서 true
    // 빌더를 통해 생성되는 Job의 기본 상태가 Active가 되어 true가 된다

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

 

CoroutineContext에 같은 키를 가진 또 다른 원소가 더해진다면, map 처럼 새로운 원소가 기존 원소를 대체합니다.

fun main() {
    val ctx1: CoroutineContext = CoroutineName("Name1")
    println(ctx1[CoroutineName]?.name)  // Name1

    val ctx1: CoroutineContext = CoroutineName("Name2")
    println(ctx1[CoroutineName]?.name)  // Name2

    val ctx3 = ctx1 + ctx2
    println(ctx3[CoroutineName]?.name)  // Name2
}

비어 있는 CoroutineContext

CoroutineContext는 컬렉션이므로 빈 컨텍스트 또한 만들 수 있습니다.  빈 컨텍스트는 원소가 없기에, 아무런 영향을 주지 않습니다.

fun main() {
    val empty: CoroutineContext = EmptyCoroutineContext
    println(empty[CoroutineName])       // null
    println(empty[Job])                 // null
    
    val ctxName = empty + CoroutineName("Name1") + empty
    println(ctxName[CoroutineName])     // CoroutineName(Name1)
}

 

원소 제거

minusKey 함수를 이용하여 원소를 CoroutineContext에서 제거할 수 있다.

fun main() {
    val ctx = CoroutineName("Name1") + Job()
    println(ctx[CoroutineName]?.name)   // Name1
    println(ctx[Job]?.isActive)         // true

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

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

CoroutineContext 폴딩(누산)

- 누산기의 첫번째 원소

- 누산기의 현재 상태와 실행되고 있는 원소로 다음 상태를 계산.

fun main() {
    val ctx = CoroutineName("Name1") + Job()

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

 

CoroutineContext 와 빌더

CoroutineContext는 코루틴의 데이터를 저장하고 전달하는 방법.

부모는 기본적으로 CoroutineContext를 자식에게 전달하고, 자식은 부모로부터 CoroutineContext를 상속받는다.

import kotlinx.coroutines.*


fun main() = runBlocking(CoroutineName("main")) {
    log("Started")
    val v1 = async {
        delay(500)
        log("Running async")
        42
    }

    launch {
        delay(1000)
        log("Running launch")
    }
    log("answer : ${v1.await()}")
}

//[main] Started
//[main] Running async
//[main] answer : 42
//[main] Running launch


fun CoroutineScope.log(msg: String) {
    val name = coroutineContext[CoroutineName]?.name
    println("[$name] $msg")
}

 

자식은 빌더의 인자에서 정의된 특정 Context를 가질수 있다.

인자로 전달된 컨텍스트는 부모로부터 상속받은 Context를 대체한다. 

import kotlinx.coroutines.*


fun main() = runBlocking(CoroutineName("main")) {
    log("Started")
    val v1 = async(CoroutineName("c1")) {
        delay(500)
        log("Running async")
        42
    }

    launch(CoroutineName("c2")) {
        delay(1000)
        log("Running launch")
    }
    log("answer : ${v1.await()}")
}

//[main] Started
//[c1] Running async
//[main] answer : 42
//[c2] Running launch


fun CoroutineScope.log(msg: String) {
    val name = coroutineContext[CoroutineName]?.name
    println("[$name] $msg")
}

 

 

* defaultContext + parentContext + childContext

 

중단함수에서 Context에 접근하기

중단함수에서 부모의 Context에 접근하는 것이 가능합니다.

coroutineContext 프로퍼티는 모든 중단 스코프에서 사용이 가능하며, 이를 통해 Context에 접근할 수 있다.

import kotlinx.coroutines.*
import kotlin.coroutines.coroutineContext


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

//Outer
//Inner
//Outer

suspend fun printName() = println(coroutineContext[CoroutineName]?.name)

 

컨텍스트를 개별적으로 생성하기 (CustomContext)

CoroutineContext.Element 구현하기.

class MyCustomContext : CoroutineContext.Element {
    
    override val key: CoroutineContext.Key<*> = Key
    
    companion object Key : CoroutineContext.Key<MyCustomContext>
}

 

CoroutineContext

- Collection이랑 매우 개념적으로 비슷하다.

- Element로 인덱싱된 집합이며, Element도 CoroutineContext다.

- CoroutineContext는 Element를 유일하게 식별하는 key를 가진다.

- 코루틴의 상태가 어떤지, 어떤 스레드에서 작동하는지 등 코루틴의 동작방식을 정한다.