Structured Concurrency
In this page:
Parents Wait for Their Children
A coroutine scope, such as the one created by runBlocking, does not complete until every coroutine launched inside it has also finished, tying child task lifetimes to their parent.
Warning: This example uses kotlinx.coroutines, which is not part of the plain Kotlin standard library. It requires the kotlinx-coroutines-core dependency (added via Gradle/Maven) to compile and run.
Example: Parents Wait for Their Children
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(50)
println("Child coroutine finished")
}
println("Parent reached its last line, but will wait for the child")
}
Login to try C/C++/Java/PHP code in the editor
coroutineScope Groups Related Work
coroutineScope { } creates a new scope for launching coroutines that is tied to the calling coroutine's context, and it suspends until every coroutine started inside it completes.
Warning: This example uses kotlinx.coroutines, which is not part of the plain Kotlin standard library. It requires the kotlinx-coroutines-core dependency (added via Gradle/Maven) to compile and run.
Example: coroutineScope Groups Related Work
import kotlinx.coroutines.*
suspend fun loadData() = coroutineScope {
launch {
delay(30)
println("Loaded part 1")
}
launch {
delay(30)
println("Loaded part 2")
}
}
fun main() = runBlocking {
loadData()
println("All data loaded")
}
Login to try C/C++/Java/PHP code in the editor
Cancellation Propagates to Children
Cancelling a parent job automatically cancels every child coroutine launched under it, ensuring related work doesn't keep running pointlessly after its parent has stopped caring about the result.
Warning: This example uses kotlinx.coroutines, which is not part of the plain Kotlin standard library. It requires the kotlinx-coroutines-core dependency (added via Gradle/Maven) to compile and run.
Example: Cancellation Propagates to Children
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
launch {
delay(1000)
println("This should not print")
}
delay(20)
}
delay(50)
job.cancelAndJoin()
println("Parent and child both cancelled")
}
Login to try C/C++/Java/PHP code in the editor
Why Structured Concurrency Matters
By tying every coroutine's lifetime to a well-defined scope, structured concurrency avoids the classic problem of background tasks silently outliving the operation that started them, which is a common source of subtle bugs in unstructured async code.
Warning: This example uses kotlinx.coroutines, which is not part of the plain Kotlin standard library. It requires the kotlinx-coroutines-core dependency (added via Gradle/Maven) to compile and run.
Example: Why Structured Concurrency Matters
import kotlinx.coroutines.*
suspend fun fetchBoth(): Pair<Int, Int> = coroutineScope {
val a = async { delay(20); 10 }
val b = async { delay(20); 20 }
Pair(a.await(), b.await())
}
fun main() = runBlocking {
val (x, y) = fetchBoth()
println("x=$x, y=$y")
}
Login to try C/C++/Java/PHP code in the editor
- Launching coroutines from a global, unscoped context (like
GlobalScope) out of habit, losing the automatic cancellation and lifecycle management structured concurrency provides. - Assuming a parent coroutine finishes as soon as its own body's last line runs, forgetting it actually waits for all its child coroutines to complete first.
- Not handling
CancellationExceptionspecially when catching exceptions broadly, which can accidentally swallow legitimate coroutine cancellation.
- In structured concurrency, every coroutine is launched within a scope, and a parent coroutine does not complete until all its children do.
- Cancelling a parent coroutine automatically cancels all of its child coroutines.
coroutineScope { }creates a new scope tied to its calling context, waiting for all coroutines launched inside it to finish.- Structured concurrency prevents leaked coroutines that keep running unexpectedly after their logical parent has already finished.
Chapter Quiz — Complete all 7 topics to unlock
0/7 topics done
Complete these topics first: