A child coroutine is launched inside another coroutine’s scope, typically with launch or async, and becomes associated with that parent automatically. This relationship lets the parent track completion without manually awaiting every descendant. section 15.1
Listing 15.1 Starting a number of different coroutines
fun main() {
runBlocking { // this: CoroutineScope #1
launch { // this: CoroutineScope #2
delay(1.seconds)
launch {
delay(250.milliseconds)
log("Grandchild done")
}
log("Child 1 done!")
}
launch {
delay(500.milliseconds)
log("Child 2 done!")
}
log("Parent done!")
}
}
Run the example to observe a parent finishing its body before its children, while runBlocking still waits for the child and grandchild coroutines. section 15.1
Use the parent-child relationship to reason about lifecycle and failure:
- Structured concurrency keeps the parent active until all child coroutines and their descendants complete. section 15.1
- A child’s uncaught exception propagates to its parent, making the parent responsible for handling it. section 18.1
- With a regular Job, a failed child causes the parent to fail and cancels its sibling coroutines to avoid unnecessary work. section 18.2.1
- With a SupervisorJob, the parent supervises children so one child’s failure need not fail the parent or its other children. section 18.2
A failing child cancels its sibling
A runBlocking block launches a heartbeat coroutine and a second coroutine that throws after one second. The heartbeat receives cancellation, prints its termination, and the parent reports the original exception. section 18.2.1
