코틀린/Kotlin Coroutine

[Kotlin][Coroutine] CoroutineContext

sxunea 2024. 7. 2. 22:39

 

 

 

본 글은 조세영님의 <코틀린 코루틴의 정석>을 읽고 이를 바탕으로 작성되었습니다.

 

 

코루틴 빌더 lanch, async의 내부 코드

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

 

 

앞선 포스트들의 코루틴 빌더 함수들의 내부 코드를 보면 coroutineContext가 반복적으로 등장한다. 이번 포스트에서는 이 coroutineContext가 무엇인지, 또 어떻게 구성하고 제거하고 접근하는지에 대해 알아보도록 하자. 

 

 

 

CoroutineContext

CoroutineContext는 코루틴을 실행하는 실행환경을 설정하고 관리하는 인터페이스이다. 

 

 

CoroutineContext의 구성요소

CoroutineContext의 구성요소는 다음과 같고, 싱글톤 객체로 키 - 값의 구조를 이루고 있다. 

  • CoroutineName : 코루틴의 이름
  • CoroutineDispatcher : 코루틴을 스레드에 할당해 실행
  • Job : 코루틴의 추상체로 코루틴을 조작하는데 사용
  • CoroutineExceptionHandler : 코루틴에서 발생한 예외 처리

 

 

생성하기, 덮어쓰기

  • '+' 연산자로 이어서 구성할 수 있다.
  • 각 키-값은 고유하므로 덮어쓰는 경우 나중에 들어온 값만을 취한다
fun main() = runBlocking<Unit> {
  val coroutineContext: CoroutineContext = newSingleThreadContext("MyThread") + CoroutineName("MyCoroutine")

// 여기서는 coroutineDispatcher, coroutineName 두 개 구성함 나머지는 미설정
  launch(context = coroutineContext) {
    println("[${Thread.currentThread().name}] 실행")
  }
}

 

 

 

합치기

val coroutineContext1 = CoroutineName("MyCoroutine1") + newSingleThreadContext("MyThread1")
val coroutineContext2 = CoroutineName("MyCoroutine2") + newSingleThreadContext("MyThread2")
val combinedCoroutineContext = coroutineContext1 + coroutineContext2

 

이 경우 coroutineContext1는 coroutineContext2에 의해 덮어씌워진다. 

 

 

 

 

접근하기

  • 구성요소의 key 프로퍼티는 동반객체로 선언된 Key와 동일한 객체를 가리킨다
  • 아래의 프린트문 세가지가 모두 같은 객체를 가리킨다는 것을 꼭 보고 넘어가자
fun main() = runBlocking<Unit> {
    val coroutineName : CoroutineName = CoroutineName("MyCoroutine")
    val dispatcher : CoroutineDispatcher = Dispatchers.IO
    val coroutineContext = coroutineName + dispatcher

    println(coroutineContext[CoroutineName]) // CoroutineName("MyCoroutine")
    println(coroutineContext[CoroutineName.Key]) // CoroutineName("MyCoroutine")
    println(coroutineContext[coroutineName.key]) // CoroutineName("MyCoroutine")
    println(coroutineContext[dispatcher.key]) // Dispatchers.IO
}

 

 

 

 

제거하기

  • minusKey : 구성요소의 키를 인자로 받아 해당 구성 요소를 제거한 coroutineContext 키를 반환한다
  • 이때 minusKey를 호출한 coroutineContext 객체는 유지되고, 구성 요소가 제거된 새로운 coroutineContext가 반환된다는 것
@OptIn(ExperimentalStdlibApi::class)
fun main() = runBlocking<Unit> {
    val coroutineName = CoroutineName("MyCoroutine")
    val dispatcher = Dispatchers.IO
    val job = Job()
    val coroutineContext: CoroutineContext = coroutineName + dispatcher + job

    val deletedCoroutineContext = coroutineContext.minusKey(CoroutineName)

    println("[deletedCoroutineContext] name : " + deletedCoroutineContext[CoroutineName] + ", dispatcher : " + deletedCoroutineContext[CoroutineDispatcher] + ", job : " + deletedCoroutineContext[Job])
    println("[coroutineContext] name : " + coroutineContext[CoroutineName] + ", dispatcher : " + coroutineContext[CoroutineDispatcher] + ", job : " + coroutineContext[Job])
}

//[deletedCoroutineContext] name : null, dispatcher : Dispatchers.IO, job : JobImpl{Active}@4b4523f8
//[coroutineContext] name : CoroutineName(MyCoroutine), dispatcher : Dispatchers.IO, job : JobImpl{Active}@4b4523f8

 

위의 예제를 보면, coroutineContext에서 minusKey를 해주고나면 deletedCoroutineContext는 제거가 완료된 새롭게 반환된 컨텍스트이고, 기존 coroutineContext는 제거 없이 기존 구성 요소를 유지하는 것을 알 수 있다.