Coroutine ‐ Structured Concurrency - thought-corner/Backend-PlayGround GitHub Wiki
- 부모 코루틴은 자식 코루틴에게 실행 환경을 상속한다.
- 부모 코루틴이 자식 코루틴을 생성하면 부모 코루틴의
CoroutineContext가 자식 코루틴에게 전달된다.
fun main() = runBlocking<Unit> {
// 1. 단일 스레드 디스패처와 코루틴 이름이 결합된 커스텀 컨텍스트 생성
val coroutineContext: CoroutineContext = newSingleThreadContext("MyThread") + CoroutineName("CoroutineA")
// 2. 부모 코루틴 생성 (커스텀 컨텍스트 주입)
launch(coroutineContext) {
println("[${Thread.currentThread().name}] 부모 코루틴 실행")
// 3. 자식 코루틴 생성 (컨텍스트를 명시하지 않음 -> 부모로부터 상속)
launch {
println("[${Thread.currentThread().name}] 자식 코루틴 실행")
}
}
}
- 자식 코루틴을 생성하는 코루틴 빌더 함수에 새로운
CoroutineContext객체가 전달되면 부모 코루틴에서 전달받은CoroutineContext구성 요소들은 자식 코루틴 빌더 함수로 전달된CoroutineContext객체의 구성 요소들로 덮어 씌여진다.
fun main() = runBlocking<Unit> {
// 1. 단일 스레드 디스패처와 부모 이름을 결합한 컨텍스트 정의
val coroutineContext: CoroutineContext = newSingleThreadContext("MyThread") + CoroutineName("ParentCoroutine")
// 2. 부모 코루틴 생성 및 실행
launch(coroutineContext) {
println("[${Thread.currentThread().name}] 부모 코루틴 실행")
// 3. 자식 코루틴 생성 시 새로운 CoroutineName을 인자로 주입 (우선순위 조작)
launch(CoroutineName("ChildCoroutine")) {
println("[${Thread.currentThread().name}] 자식 코루틴 실행")
}
}
}-
launch()나async()를 포함한 모든 코루틴 빌더 함수는 호출 시마다 코루틴 추상체인Job객체를 새롭게 생성한다. - 코루틴 제어에
Job객체가 필요하기 때문에Job객체를 부모 코루틴으로부터 상속받지 않는다. - 즉, 코루틴 빌더를 통해 생성된 코루틴들은 서로 다른
Job을 가진다.
- 코루틴의 구조화는 하나의 큰 비동기 작업을 작은 비동기 작업으로 나눌 때 일어난다.
- 코루틴으로 취소가 요청되면 자식 코루틴에 취소가 전파된다.
- 부모 코루틴은 모든 자식 코루틴이 실행 완료되어야 완료될 수 있다.
- 코루틴의 구조화는 큰 작업을 연관된 여러 작은 작업으로 나누는 방식으로 일어나는데 작은 작업이 완료되어야 큰 작업이 완료될 수 있기 때문이다.
- 실행 완료 중(Completing) 상태란 부모 코루틴의 모든 코드가 실행됐지만, 자식 코루틴이 실행 중인 경우 부모 코루틴이 갖는 상태이다.
- 부모 코루틴은 더 이상 실행할 코드가 없더라도 자식 코루틴들이 모두 완료될 때까지 기다리는데 이 때, '실행 완료 중' 상태에 머무른다.
- '실행 완료 중' 상태의 부모 코루틴은 자식 코루틴이 모두 완료되면 자동으로 '실행 완료' 상태로 바뀐다.
public interface CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
public val coroutineContext: CoroutineContext
}-
CoroutineScope는 내부에 코루틴 실행 환경인CoroutineContext를 가지는 단순한 인터페이스이다. -
CoroutineScope인터페이스를 구현한 구체 클래스를 사용하면CoroutineScope객체를 생성할 수 있다.
-
CoroutineScope를 취소하려면 다음과 같이 취소할 수 있다.
-
CoroutineScope객체는 현재CoroutineScope이 활성화 되어있는지 확인하는isActive프로퍼티를 제공한다. -
isActive프로퍼티는CoroutineContext로부터Job객체를 꺼내isActive를 확인한다.
fun main() = runBlocking<Unit> {
// 1. Dispatchers.Default 백그라운드 트랙에서 무한 루프를 도는 자식 코루틴 생성
val whileJob: Job = launch(Dispatchers.Default) {
// 2. 주기적으로 현재 코루틴 스코프의 활성화 상태(isActive)를 체크
while (this.isActive) {
println("작업 중")
}
}
// 3. 메인 스레드에서 100ms 동안 대기하며 자식 코루틴이 동작할 시간을 줌
delay(100L)
// 4. 자식 코루틴에 취소(cancel) 시그널 전파
whileJob.cancel()
}-
runBlocking()함수를 호출하면 부모Job이 없는 루트Job객체가 생성된다.- 루트
Job: 부모Job객체가 없는 구조화의 시작점 역할을 하는Job객체 - 루트 코루틴 : 이
Job객체에 의해 제어되는 코루틴
- 루트
-
CoroutineScope생성 함수는 새로운 루트Job을 가진CoroutineContext를 생성한다.
-
Job생성 함수로 새로운 루트Job을 생성할 수 있다.
-
Job생성 함수는 부모Job을 인자로 받을 수 있다.- 만약 parent 인자가 입력되지 않으면
parent = null이 되어 루트Job이 생성된다. - 만약 parent 인자가 입력되면 해당
Job을 부모로 하는Job이 생성된다.
- 만약 parent 인자가 입력되지 않으면
/**
* Creates a job object in an active state.
* @param parent optional parent job to enforce structured concurrency.
*/
public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)
- 이 때, 생성함수로 생성된
Job객체는 자동으로 실행 완료되지 않기 때문에 명시적으로complete()함수를 호출해야 한다.