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 |