부모 Coroutine의 책임
부모 Coroutine은 언제나 자식들이 완료될 때까지 기다린다. 부모는 모든 자식들의 실행을 명시적으로 추적하지 못하고, 그들이 모두 끝날 때까지 기다리기 위해 Job.join을 사용할 필요가 없다.
// launch a coroutine to process some kind of incoming request
val request = launch {
repeat(3) { i -> // launch a few children jobs
launch {
delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
println("Coroutine $i is done")
}
}
println("request: I'm done and I don't explicitly join my children that are still active")
}
request.join() // wait for completion of the request, including all its children
println("Now processing of the request is complete")
📌 전체 코드는 이곳에서 확인할 수 있습니다.
결과는 다음과 같다 :
request: I'm done and I don't explicitly join my children that are still active
Coroutine 0 is done
Coroutine 1 is done
Coroutine 2 is done
Now processing of the request is complete
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 단, 부모 Scope 내부이더라도 CoroutineScope(context: CoroutineContext) 함수를 통해 CoroutineScope이 재정의되면 다른 Scope에서 launch 된 Job이 끝날 때까지 기다리지 않는다.
예를 들어 다음의 코드를 보자
fun main() = runBlocking<Unit> {
CoroutineScope(Dispatchers.IO).launch {
delay(2000L)
println("b")
}
launch {
delay(1000L)
println("a")
}
}
이에 대한 출력은 다음과 같다. a가 출력되는 순간 runBlocking Coroutine이 종료되어 상위 Coroutine이 취소되면서 별도 Scope에서 실행되는 Coroutine 또한 종료된다. 즉 부모는 별도 Scope으로 정의된 Coroutine은 기다리지 않는다.
a
Process finished with exit code 0
여기서 헷갈리지 말아야 할 것은 CoroutineScope(Dispatcher.IO)에서 실행된 Coroutine이 부모에 의해 취소되지는 않는다는 점이다. 단순히 main 함수가 마지막에 도달해 프로그램이 종료되며 취소되는 것일 뿐이다.
아래 코드를 보자. 아래 코드에서는 처음 수행한 runBlocking이 종료되더라도 main함수가 종료되지 않도록 뒤에 runBlocking을 하나 더 두었다.
fun main() {
runBlocking<Unit> {
CoroutineScope(Dispatchers.IO).launch {
delay(2000L)
println("b")
}
launch {
delay(1000L)
println("a")
}
}
runBlocking {
delay(3000L)
}
}
이 코드의 출력은 다음과 같다. CoroutineScope(Dispatcher.IO)에서 수행된 Coroutine의 결과값인 "b"가 출력되는 것을 확인할 수 있다.
a
b
Process finished with exit code 0
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutine context and dispatchers - Parental responsibilities
원문 최종 수정 : 2022년 6월 27일
디버깅을 위해 Coroutines에 이름 짓기
자동으로 설정된 ID들은 Coroutine들이 자주 로깅될 때 유용하며, 같은 Coroutine에서 오는 연관된 로그 기록들을 연관짓기만 하면 된다. 하지만, Coroutine이 특정한 요청이나 특정한 백그라운드 작업을 하는 경우 디버깅 목적을 위해 이름을 설정하는 것이 좋다. Context의 요소인 CoroutineName는 스레드의 이름과 같은 용도로 사용된다. 이는 디버깅 모드가 켜져 있을 때 Coroutine을 실행하는 스레드 이름에 포함된다.
다음 예시는 이 개념에 대해 보여준다 :
log("Started main coroutine")
// run two background value computations
val v1 = async(CoroutineName("v1coroutine")) {
delay(500)
log("Computing v1")
252
}
val v2 = async(CoroutineName("v2coroutine")) {
delay(1000)
log("Computing v2")
6
}
log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
📌 전체 코드는 이곳에서 확인할 수 있습니다.
JVM 옵션에 -Dkotlinx.coroutines.debug 를 넣은 상태*1로 생성한 결과는 다음과 유사하다 :
[main @main#1] Started main coroutine
[main @v1coroutine#2] Computing v1
[main @v2coroutine#3] Computing v2
[main @main#1] The answer for v1 / v2 = 42
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. -Dkotlinx.coroutines.debug 를 넣지 않으면 다음 결과가 출력된다.
[main] Started main coroutine
[main] Computing v1
[main] Computing v2
[main] The answer for v1 / v2 = 42
-Dkotlinx.coroutines.debug 를 설정하는 방법은 다음글을 참조하자.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutine context and dispatchers - Naming coroutines for debugging
원문 최종 수정 : 2022년 6월 27일
Context 요소들 결합하기
종종 Coroutine Context에 복수의 요소들을 정의해야 할 수 있다. 우리는 이를 위해 + 연산자를 사용할 수 있다*1. 예를 들어, 명시적으로 Dispatcher을 지정함과 동시에 명시적으로 이름을 지정한 Coroutine을 실행해야 할 수 있다 :
launch(Dispatchers.Default + CoroutineName("test")) {
println("I'm working in thread ${Thread.currentThread().name}")
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
JVM Option에 -Dkotlinx.coroutines.debug 을 추가한*2 코드의 결과값은 다음과 같다 :
I'm working in thread DefaultDispatcher-worker-1 @test#2
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. + 연산자로 Coroutine Context를 결합할 수 있는 이유는 CoroutineContext에 operator fun plus가 정의되어 있기 때문이다.
public operator fun plus(context: CoroutineContext): CoroutineContext =
...
*2. JVM 옵션 설정 방법은 다음글을 참조하면 된다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutine context and dispatchers - Combining context elements
원문 최종 수정 : 2022년 6월 27일
목차로 돌아가기