Thread 전환 하기
다음 코드를 JVM option에 -Dkotlinx.coroutines.debug를 넣어 실행시켜보자(debug 확인).
newSingleThreadContext("Ctx1").use { ctx1 ->
newSingleThreadContext("Ctx2").use { ctx2 ->
runBlocking(ctx1) {
log("Started in ctx1")
withContext(ctx2) {
log("Working in ctx2")
}
log("Back to ctx1")
}
}
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이는 몇가지 새로운 기술들을 보여준다. 하나는 runBlocking을 명시적으로 구체화된 Context와 함께 사용하는 것이고, 다른 하나는 아래 출력 에서 볼 수 있듯이 withContext 함수를 같은 Coroutine에 계속 있으면서 Context를 바꾸는데 사용하는 것이다.
[Ctx1 @coroutine#1] Started in ctx1
[Ctx2 @coroutine#1] Working in ctx2
[Ctx1 @coroutine#1] Back to ctx1
또한 이 예제에서는 Kotlin 표준 라이브러리의 use 함수를 newSingleThreadContext에 의해 생성된 스레드들이 더이상 필요하지 않을 때 해제하는데 사용한다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutine context and dispatchers - Jumping between threads
원문 최종 수정 : 2022년 6월 27일
Context 내부의 Job
Coroutines의 Job은 Context의 구성요소이고, coroutineContext[Job]*1 표현식으로부터 가져올 수 있다.
fun main() = runBlocking<Unit> {
println("My job is ${coroutineContext[Job]}")
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
디버그 모드에서 출력은 다음과 같다.
My job is "coroutine#1":BlockingCoroutine{Active}@6d311334
CoroutineScope의 isActive는 coroutineContext[Job]?.isActive == true 를 편리하게 사용하기 위한 축약어*2임을 참고하자.
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. CoroutineContext는 내부 요소들을 Map 형태로 관리한다. Map의 Key값에 Job이 들어가면 CoroutineContext의 Job 구성요소가 추출된다.
public interface Job : CoroutineContext.Element {
...
public companion object Key : CoroutineContext.Key<Job>
...
}
*2. CoroutineScope.isActive 확장 함수는 CoroutineScope의 coroutineContext에서 Job 객체를 가져온 다음 Job이 isActive인지 체크하며, 만약 null이라면 true를 리턴한다.
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
public val CoroutineScope.isActive: Boolean
get() = coroutineContext[Job]?.isActive ?: true
즉, coroutineScope 내의 CoroutineContext는 하나이며, CoroutineContext 내부에는 CoroutineContext.Key를 상속 받는 Key 값과 해당 Key 값에 대응되는 Value 값으로 구성요소들을 관리한다. 이 구성요소는 Coroutine 현재 실행중인지 판단하는 Job을 포함한다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutine context and dispatchers - Job in the context
원문 최종 수정 : 2022년 6월 27일
Coroutine의 자식들
Coroutine이 다른 Coroutine의 CoroutineScope내에서 실행되면 Context를 CoroutineScope.coroutineContext를 통해 상속 받고 새로운 Coroutine의 Job은 부모 Coroutine의 Job의 자식이 된다. 부모 Coroutine이 취소되면, 자식 Coroutine들 또한 재귀적으로 취소된다.
하지만, 부모-자식 관계는 두가지 방법으로 명시적으로 재정의 될 수 있다.
- Coroutine을 실행할 때 두 개의 다른 Scope이 명시적으로 설정하는 경우(예를 들어, GlobalScope.launch), 부모 Scope으로부터 Job을 상속 받지 않는다.*1
- 새로운 Coroutine을 위해 다른 Job이 Context로 전달되는 경우(아래 예시와 같이) 부모 Scope의 Job을 재정의한다.*2
두 경우 모두 실행된 Coroutine은 실행된 Scope에 묶여있지 않고 독립적으로 동작한다.
// launch a coroutine to process some kind of incoming request
val request = launch {
// it spawns two other jobs
launch(Job()) {
println("job1: I run in my own Job and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
}
// and the other inherits the parent context
launch {
delay(100)
println("job2: I am a child of the request coroutine")
delay(1000)
println("job2: I will not execute this line if my parent request is cancelled")
}
}
delay(500)
request.cancel() // cancel processing of the request
println("main: Who has survived request cancellation?")
delay(1000) // delay the main thread for a second to see what happens
📌 전체 코드는 이곳에서 확인할 수 있습니다.
코드를 실행한 결과는 다음과 같다.
job1: I run in my own Job and execute independently!
job2: I am a child of the request coroutine
main: Who has survived request cancellation?
job1: I am not affected by cancellation of the request
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 아래와 같이 CoroutineScope(Dispatchers.IO) 를 통해 CoroutineScope을 재정의 한다고 해보자.
fun main() = runBlocking<Unit> {
CoroutineScope(Dispatchers.IO).launch {
...
}
}
CoroutineScope을 통해 CoroutineContext가 재정의 되면 CoroutineContext는 부모로부터 상속 받지 않게 되어 Job이 없게 된다. 따라서 아래와 같이 context의 Job이 null인 경우 context에 Job을 추가하는 방식으로 CoroutineScope이 만들어진다.
@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
ContextScope(if (context[Job] != null) context else context + Job())
이로 인해 부모 Coroutine과는 다른 Job을 가지게 되는 것이다.
*2. CoroutineContext는 구성요소를 Key-Value 값으로 관리한다. 즉, Key인 Job에 대응하는 Value는 하나뿐이다. 따라서 새로운 Job이 CoroutineContext에 설정되면 Key인 Job에 대응되는 Value값은 새로운 Job이 된다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutine context and dispatchers - Children of a coroutine
원문 최종 수정 : 2022년 6월 27일
목차로 돌아가기