본문 바로가기

코루틴

[코루틴] CoroutineContext

CoroutineContext란?

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

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

 

코루틴 빌더의 첫 번째 변수로 넣어주던 객체이다.

 

CoroutineContext

- 코루틴을 실행하는 실행 환경을 설정하고 관리하는 Interface이다.

Element들의 집합이다. 각 Element는 고유한 key를 가진다.

@SinceKotlin("1.3")
public interface CoroutineContext {
    // key에 따른 context의 Element 반환
    public operator fun <E : Element> get(key: Key<E>): E?
    // 초기값을 시작으로 주어진 람다 함수를 이용하여 Context Element들을 병합한 후 결과 반환
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R
    // 현재 Context와 주어진 다른 Context가 가지는 Element들을 모두 포함하는 CoroutineContext 반환
    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                ...
            }
    // Context에서 해당 key를 갖는 Element들을 제외한 새로운 context 반환
    public fun minusKey(key: Key<*>): CoroutineContext
    public interface Key<E : Element>
    
    public interface Element : CoroutineContext {
        public val key: Key<*>
        public override operator fun <E : Element> get(key: Key<E>): E? =
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null
        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)
        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}

 

 

CoroutineContext 구성 요소

크게 Job, CoroutineDispatcher, CoroutineName, CoroutineExceptionHandler로 이루어져 있다.

- Job : 코루틴의 추상체로 코루틴을 조작

- CoroutineDispatcher : 코루틴을 스레드에 할당

- CoroutineName : 코루틴의 이름

- CoroutineExceptionHandler : 코루틴에서 발생한 예외 처리

 

 

CoroutineContext 구성하기

Key-value로 구성 요소 관리

각 Element는 고유한 key를 가지며 중복된 값은 허용되지 않는다.

CoroutineName key CoroutineName 객체
CoroutineDispatcher key CoroutineDispatcher 객체
Job key Job 객체
CoroutineExceptionHandler key CoroutineExceptionHandler 객체

 

 

Element 추가

키에 값을 직접 대입하지 않고 + 연산자를 사용해 CoroutineContext 객체 구성

val coroutineContext: CoroutineContext = newSingleThreadContext("MyThread") + CoroutineName("MyCoroutine")

// 사용
fun main() = runBlocking<Unit> {
    val coroutineContext: CoroutineContext = newSingleThreadContext("MyThread") + CoroutineName("MyCoroutine")
    launch(context = coroutineContext) {
        println("[${Thread.currentThread().name}, ${currentCoroutineContext()[CoroutineName]} 실행]")
    }
}
// 결과
[MyThread, CoroutineName(MyCoroutine) 실행]
CoroutineName key CoroutineName("MyCoroutine")
CoroutineDispatcher key newSingleThreadContext("My Thread")
Job key X
CoroutineExceptionHandler key X

 

구성 요소가 없는 CoroutineContext는 EmptyCoroutineContext 사용

 

 

Element 덮어씌우기

같은 Element가 둘 이상 더해지면 나중에 추가된 Element가 이전의 값을 덮어씌운다.

fun main() = runBlocking<Unit> {
    val coroutineContext: CoroutineContext = newSingleThreadContext("MyThread") + CoroutineName("MyCoroutine")
    val newCoroutineContext: CoroutineContext = coroutineContext + CoroutineName("NewCoroutine")
    launch(context = newCoroutineContext) {
        println("[${Thread.currentThread().name}, ${currentCoroutineContext()[CoroutineName]} 실행]")
    }
}
// 결과
[MyThread, CoroutineName(NewCoroutine) 실행]

 

 

여러 Element로 이루어진 CoroutineContext 합치기

동일한 key를 가진 Element가 있다면 나중에 들어온 값이 선택된다.

fun main() = runBlocking<Unit> {
    val coroutineContext1: CoroutineContext = CoroutineName("MyCoroutine1") + newSingleThreadContext("MyThread1")
    val coroutineContext2: CoroutineContext = CoroutineName("MyCoroutine2") + newSingleThreadContext("MyThread2")
    val combinedContext = coroutineContext1 + coroutineContext2
    launch(context = combinedContext) {
        println("[${Thread.currentThread().name}, ${currentCoroutineContext()[CoroutineName]} 실행]")
    }
}
// 결과
[MyThread2, CoroutineName(MyCoroutine2) 실행]

 

 

CoroutineContext에 Job 생성해 추가하기

val myJob = Job()
val coroutineContext: CoroutineContext = Dispatchers.IO + myJob
CoroutineName key X
CoroutineDispatcher key Dispatchers.IO
Job key myJob
CoroutineExceptionHandler key X

 

하지만, Job 객체를 추가하면 코루틴의 구조화가 깨지기 때문에 주의가 필요하다.

 

 

CoroutineContext Element에 접근하는 방법

구성 요소에 접근하기 위해서는 key가 필요하다.

 

CoroutineContext Element 키

일반적으로 Element는 내부에 키를 싱글톤 객체로 구현한다.

public data class CoroutineName(
    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
    public companion object Key : CoroutineContext.Key<CoroutineName>
    
    override fun toString(): String = "CoroutineName($name)"
}

public interface CoroutineExceptionHandler : CoroutineContext.Element {
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
    public fun handleException(context: CoroutineContext, exception: Throwable)
}

public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

    // 아직 실험중인 API
    @ExperimentalStdlibApi
    public companion object Key : AbstractCoroutineContextKey<ContinuationInterceptor, CoroutineDispatcher>(
        ContinuationInterceptor,
        { it as? CoroutineDispatcher })
    ...
}

public interface Job : CoroutineContext.Element {
    public companion object Key : CoroutineContext.Key<Job>
}

 

 

키를 사용해 CoroutineContext Element에 접근

// Singleton Key 사용
fun main() = runBlocking<Unit> {
    val coroutineContext = CoroutineName("MyCoroutine") + Dispatchers.IO
    val nameFromContext = coroutineContext[CoroutineName.Key]
    println(nameFromContext)
}
// 결과
CoroutineName(MyCoroutine)

// Element 자체를 Key로 사용. 자동으로 CoroutineName.Key를 사용해 연산 처리
fun main() = runBlocking<Unit> {
    val coroutineContext = CoroutineName("MyCoroutine") + Dispatchers.IO
    val nameFromContext = coroutineContext[CoroutineName]
    println(nameFromContext)
}
// 결과
CoroutineName(MyCoroutine)

// Element의 key 프로퍼티를 사용해 접근
fun main() = runBlocking<Unit> {
    val coroutineName = CoroutineName("MyCoroutine")
    val dispatcher: CoroutineDispatcher = Dispatchers.IO
    val coroutineContext = coroutineName + dispatcher
    println(coroutineContext[coroutineName.key])
    println(coroutineContext[dispatcher.key])
}
// 결과
CoroutineName(MyCoroutine)
Dispatchers.IO

 

각 Element는 같은 Key 객체를 공유한다.

fun main() = runBlocking<Unit> {
    val coroutineName = CoroutineName("MyCoroutine")
    
    if (coroutineName.key === CoroutineName.Key) {
        println("coroutineName.key와 CoroutineName.Key는 동일하다")
    }
}
// 결과
coroutineName.key와 CoroutineName.Key는 동일하다

 

 

CoroutineContext Element 제거

minusKey 함수 사용

@OptIn(ExperimentalStdlibApi::class)
fun main() = runBlocking<Unit> {
    val coroutineName = CoroutineName("MyCoroutine")
    val dispatcher = Dispatchers.IO
    val myJob = Job()
    val coroutineContext: CoroutineContext = coroutineName + dispatcher + myJob
    println("${coroutineContext[CoroutineName]}, ${coroutineContext[CoroutineDispatcher]}, ${coroutineContext[Job]}")
    
    val deletedCoroutineContext = coroutineContext.minusKey(CoroutineName)
    println("${deletedCoroutineContext[CoroutineName]}, ${deletedCoroutineContext[CoroutineDispatcher]}, ${deletedCoroutineContext[Job]}")
}
// 결과
CoroutineName(MyCoroutine), Dispatchers.IO, JobImpl{Active}@a7e666
null, Dispatchers.IO, JobImpl{Active}@a7e666

 

주의할 점

minusKey 함수는 새로운 CoroutineContext 반환.

@OptIn(ExperimentalStdlibApi::class)
fun main() = runBlocking<Unit> {
    val coroutineName = CoroutineName("MyCoroutine")
    val dispatcher = Dispatchers.IO
    val myJob = Job()
    val coroutineContext: CoroutineContext = coroutineName + dispatcher + myJob
    val deletedCoroutineContext = coroutineContext.minusKey(CoroutineName)
    println("${coroutineContext[CoroutineName]}, ${coroutineContext[CoroutineDispatcher]}, ${coroutineContext[Job]}")
}
// 결과
CoroutineName(MyCoroutine), Dispatchers.IO, JobImpl{Active}@a7e666

'코루틴' 카테고리의 다른 글

[코루틴] 일시 중단 함수 - suspend  (0) 2024.09.18
[코루틴] 예외 처리  (0) 2024.09.16
[코루틴] 구조화된 동시성  (0) 2024.09.08
[코루틴] async & Deferred  (0) 2024.08.30
CoroutineDispatcher 란 무엇인가  (0) 2024.08.25