CoroutineContext에는 Job, CoroutineDispatcher, CoroutineName 이 있다고 이전 장에서 설명했다.
이 중 Job에 대해 더 자세히 알아보자.
Job
Job은 Coroutine Lifecycle를 관리하며, 부모-자식 관계의 특성을 가지고 있다.
부모 - 자식 설명
- 자식은 부모로부터 Context를 상속받는다.
- 부모는 모든 자식이 작업을 마칠 때 까지 기다린다.
- 부모 코루틴이 취소되면 자식 코루틴도 취소된다.
- 자식 코루틴에서 에러가 발생하면 부모 코루틴 또한 에러로 소멸된다.
Job Lifecycle
위의 이미지에 대해 알 수 있는 항목은
- default 상태인 Active 상태는 Job을 실행한다.
- 주어진 작업이 끝나면 Compleing 상태에서 자식이 모두 끝날 때 까지 기다린다.
- 자식들이 모두 끝난다면 Completed 상태로 끝이 난다.
- 만약 Job이 실행되는 도중이 취되거나 실패하면 Cancelling 상태로 가게 된다. 이 경우 어떻게 처리할 지 작업할 수 있다.
- 작업이 끝난다면 Cancelled 상태로 끝이 난다.
해당 상태는 Job의 toString 함수로 알 수 있다.
fun main() {
runBlocking {
val job = Job()
println(job)
job.complete()
println(job)
}
}
// 결과
JobImpl{Active}@43814d18
JobImpl{Completed}@43814d18
상태 확인
State | isActive | isCompleted | isCancelled |
New (지연 시작될 때 시작 상태) | false | false | false |
Active (시작 상태 기본 값) | true | false | false |
Completing (일시적인 상태) | true | false | false |
Cancelling (일시적인 상태) | false | false | true |
Cancelled (최종 상태) | false | true | true |
Completed (최종 상태) | false | true | false |
Job 과 자식들간의 관계
모든 코루틴은 자신만의 Job을 생성하며, 인자 또는 부모 코루틴으로 온 잡은 새로운 Job의 부모로 사용된다.
fun main() {
runBlocking {
val name = CoroutineName("Some name")
val job = Job()
launch(name + job) {
val childName = coroutineContext[CoroutineName]
println(childName == name) //true
val childJob = coroutineContext[Job] // 새로운 잡의 부모로 대체
println(childJob == job) // false
println(childJob == job.children.first()) // true
}
}
}
// 결과
true
false
true
위 설명처럼 CoroutineBuilder 에서 반환되는 Job으로 대체되어 childJob == job 가 false 이다.
자식 기다리기
Job의 첫번째 이점은 코루틴이 완료될 때까지 기다리는데 사용될 수 있다.
이를 위해 join 함수를 사용해야 한다.
join은 Job이 최종 상태에 도달할 때 까지 기다리는 중단 함수 이다.
fun main(): Unit = runBlocking {
val job1 = launch {
delay(1000)
println("Test1")
}
val job2 = launch {
delay(2000)
println("Test2")
}
job1.join()
job2.join()
println("All tests are done")
}
// (1초 후)
// Test1
// 1초 후
// (1초 후)
// All tests are done
아래는 join함수를 사용해, All tests are done 가 출력되기 전 job1, job2 의 CoroutineBuilder 가 실행되게 만들었다.
fun main(): Unit = runBlocking {
launch {
delay(1000)
println("Test1")
}
launch {
delay(2000)
println("Test2")
}
val children = coroutineContext[Job]?.children
val childrenNum = children?.count()
println("Number of children: $childrenNum")
children?.forEach { it.join() }
println("All tests are done")
}
// 2
// (1초 후)
// Test1
// (1초 후)
// Test2
// All tests are done
자식 코루틴으로 반환되는 Job 객체를 사용하지 않아도 children 프로퍼티를 통해 접근할 수 있다.
Job 팩토리 함수
job 을 만들 때 val job = Job()형식으로 보통 만든다. 이 때 Job() 은 생성자가 아니라 사실 팩토리 함수 이다.
public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)
CompletableJob인터페이스는 두 가지 메서드를 추가하여 Job의 확장성을 높혔다.
public interface CompletableJob : Job {
public fun complete(): Boolean
public fun completeExceptionally(exception: Throwable): Boolean
}
complete() : Job을 완료하는데 사용한다.
- 모든자식 코루틴은 실행이 완료될 때 까지 실행된 상태를 유지하지만 complete 를 호출한 Job에서는 새로운 코루틴이 시작될 수 없다.
fun main() {
runBlocking {
val job = Job()
launch(job) {
repeat(5) {
delay(200L)
println("$it")
}
}
launch {
delay(500)
job.complete()
}
job.join()
launch(job) { // 실행되지 않음
println("Will not be printed")
}
println("Done")
}
}
// 결과
0
1
2
3
4
Done
completeExceptionally : 인자로 받은 예외로 Job을 완료시킨다.
- 모든 자식 코루틴은 예외를 래핑한 CancellationException으로 즉시 취소된다.
'도서 > 코틀린 코루틴' 카테고리의 다른 글
2장 코루틴 라이브러리 - 코루틴 스코프 함수 (1) | 2024.02.04 |
---|---|
2장 코루틴 라이브러리 - 예외 처리 (1) | 2024.02.03 |
2장 코루틴 라이브러리 - Cancel (0) | 2024.02.03 |
2장 코루틴 라이브러리 - CoroutineContext (1) | 2024.02.03 |
2장 코루틴 라이브러리 - Coroutine Buidler (0) | 2024.02.03 |