구조화된 동시성의 중요한 특성 중 세 가지는 Job 컨텍스트와 관련이 있다.
Job 과 연관된 코틀린 코루틴의 필수적인 작동 방식에 대해 알아보자!
fun main(): Unit = runBlocking(CoroutineName("main")) {
val name = coroutineContext[CoroutineName]?.name
println(name) // main
launch {
delay(1000)
val name = coroutineContext[CoroutineName]?.name
println(name) // main
}
}
Job은 인터페이스이지만 구체적인 사용법과 상태를 가지고 있다는 점에서 추상 클래스처럼 다룰 수도 있음
suspend fun main() = coroutineScope {
// 빌더로 생성된 잡은
val job = Job()
println(job) // JobImpl{Completed}@ADD
// 메서드로 완료시킬 때까지 Active 상태입니다.
job.complete()
println(job) // JobImpl{Completed}@ADD
// launch는 기본적으로 활성화되어 있습니다.
val activeJob = launch {
delay(1000)
}
println(activeJob) // StandaloneCoroutine{Active}@ADD
// 여기서 잡이 완료될 때까지 기다립니다.
activeJob.join() // (1초 후)
println(activeJob) // StandaloneCoroutine{Active}@ADD
// launch는 New 상태로 지연 시작됩니다.
val lazyJob = launch(start = CoroutineStart.LAZY) {
delay(1000)
}
println(lazyJob) // LazyStandaloneCoroutine{New}@ADD
lazyJob.start()
println(lazyJob) // LazyStandaloneCoroutine{New}@ADD
lazyJob.join() // (1초 후)
println(lazyJob) // LazyStandaloneCoroutine{New}@ADD
}
| 상태 | 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을 생성하며 인자 또는 부모 코루틴으로부터 온 잡은 새로운 잡의 부모로 사용됨fun main(): Unit = 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
}
}
fun main(): Unit = runBlocking {
val job: Job = launch {
delay(1000)
}
val parentJob: Job = coroutineContext.job
// 또는 coroutineContext[Job]!!
println(job == parentJob) // false
val parentChildren: Sequence<Job> = parentJob.children
println(parentChildren.first() == job) // true
}
fun main(): Unit = runBlocking {
launch(Job()) { // 새로운 잡이 부모로부터 상속받은 잡을 대체
delay(1000)
println("Will not be printed")
}
}
// (아무것도 출력하지 않고, 즉시 종료됨)
runBlocking 과는 아무런 관련이 없게 됨잡의 첫 번째 중요한 이점 : 코루틴이 완료될 때까지 기다리는 데 사용될 수 있음
join은 지정한 잡이 Completed나 Cancelled와 같은 마지막 상태에 도달할 때까지 기다리는 중단 함수
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초 후)
// Test2
// All tests are done
// Job 인터페이스는 모든 자식을 참조할 수 있는 children 프로퍼티를 노출
// 모든 자식이 마지막 상태가 될 때까지 기다리는데 활용 가능
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")
}
// Number of children: 2
// (1초 후)
// Test1
// (1초 후)
// Test2
// All tests are done
Job은 Job() 팩토리 함수를 사용해서 코루틴 없이 Job을 만들 수 있음
팩토리 함수로 생성하는 잡은 어떤 코루틴과도 연관되지 않으며 컨텍스트로 사용될 수 있음
한 개 이상의 자식 코루틴을 가진 부모 잡으로 사용할 수 있음
‼️
Job()팩토리 함수를 사용해 잡을 생성하고 다른 코루틴의 부모로 지정한 뒤에join을 호출하면 모든 작업이 끝마쳐도Job이 여전히 액티브 상태에 있어 프로그램이 종료되지 않음
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
delay(1000)
println("Text 1")
}
launch(job) {
delay(2000)
println("Text 2")
}
job.join() // 여기서 영원히 대기하게 됨
println("Will not be printed")
}
suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
delay(1000)
println("Text 1")
}
launch(job) {
delay(2000)
println("Text 2")
}
job.children.forEach { it.join() }
}
Job()은 가짜 생성자 (실제 반환 타입은 Job이 아니라 하위 인터페이스인 CompletableJob)public fun Job(parent: Job? = null): CompletableJob
complete(): Booleancomplete 메서드를 사용하면 모든 자식 코루틴은 작업이 완료될 때까지 실행된 상태를 유지하지만, complete를 호출한 잡에서 새로운 코루틴이 시작될 수는 없음fun main() = runBlocking {
val job = Job()
launch(job) {
repeat(5) { num ->
delay(200)
println("Rep$num")
}
}
launch {
delay(500)
job.complete()
}
job.join()
launch(job) {
println("Will not be printed")
}
println("Done")
}
// Rep0
// Rep1
// Rep2
// Rep3
// Rep4
// Done
completeExceptionally(exception: Throwable): BooleancancellationException으로 즉시 취소fun main() = runBlocking {
val job = Job()
launch(job) {
repeat(5) { num ->
delay(200)
println("Rep$num")
}
}
launch {
delay(500)
job.completeExceptionally(Error("Some error"))
}
job.join()
launch(job) {
println("Will not be printed")
}
println("Done")
}
// Rep0
// Rep1
// Done
complete 함수는 잡의 마지막 코루틴을 시작한 후 자주 사용됨join 함수를 사용해 잡이 완료되는 걸 기다리기만 하면 됨suspend fun main(): Unit = coroutineScope {
val job = Job()
launch(job) {
delay(1000)
println("Text 1")
}
launch(job) {
delay(2000)
println("Text 2")
}
job.complete()
job.join()
}
// (1초 후)
// Text 1
// (1초 후)
// Text 2
Job 함수의 인자로 부모 잡의 참조값을 전달 할 수 있음suspend fun main(): Unit = coroutineScope {
val parentJob = Job()
val job = Job(parentJob)
launch(job) {
delay(1000)
println("Text 1")
}
launch(job) {
delay(2000)
println("Text 2")
}
delay(1100)
parentJob.cancel()
job.children.forEach { it.join() }
}
// Text 1