Structured concurrency.
Coroutines can launch other coroutines. But what happens when things go wrong? Who cleans up? Today we understand structured concurrency, the principle that makes concurrent code manageable and safe.
Without structure, concurrency becomes chaos.
A function launches a background task. The function returns. The task keeps running. Who tracks it? Who cancels it when no longer needed?
Unstructured concurrency leads to leaks. Tasks run forever. Resources stay allocated. Memory grows.
Unstructured concurrency leads to confusion. You do not know what is running. You cannot reason about the system state.
We need structure. We need clear relationships. We need automatic cleanup.
In structured concurrency, coroutines form a hierarchy.
When a coroutine launches another, they become parent and child. The parent knows about the child. The relationship is explicit.
A scope is the parent. All coroutines launched in a scope are its children. The scope tracks them.
This hierarchy is automatic. When you call launch or async inside a coroutine, the new coroutine becomes a child of the current one.
The hierarchy enables management. Cancel the parent, children cancel. Parent waits for children. No orphans.
A scope determines how long coroutines live.
The scope completes only when all its children complete. runBlocking does not return until all launched coroutines finish.
The scope cancels when cancelled. All children cancel with it.
This is the contract. Children cannot outlive their parent scope. When the scope ends, children end.
This prevents leaks. You cannot forget about a coroutine. The scope remembers and ensures cleanup.
Cancellation flows down the hierarchy.
Cancel a parent, all descendants cancel. Cancel a scope, all its coroutines cancel.
This is intentional. If a parent is cancelled, its work is no longer needed. Children doing work for the parent should also stop.
Cancellation is cooperative. A cancelled coroutine does not stop immediately. It stops at the next suspension point if checking for cancellation.
Well-written suspend functions check for cancellation. delay checks. Network calls check. Your long computations should check.
Failure also propagates, but differently.
If a child fails with an exception, the failure propagates up. The parent is notified. The parent cancels other children. The parent fails.
This is called failure propagation. One failure brings down the whole tree.
Why? Because children typically do related work. If one part fails, the whole operation is likely compromised. Better to fail fast than continue with partial results.
This is the default behavior. Failure goes up, cancellation goes down.
coroutineScope creates a scope that follows structured concurrency.
It suspends until all children complete. It propagates failures. It cancels children if cancelled.
Use coroutineScope to group related operations. Launch multiple coroutines inside. They complete together or fail together.
coroutineScope is a suspend function. The calling coroutine suspends until the scope completes.
This is different from runBlocking. runBlocking blocks the thread. coroutineScope suspends the coroutine.
Sometimes you want children to fail independently.
supervisorScope creates a scope where child failures do not propagate up or cancel siblings.
One child fails, others continue. The supervisor scope does not fail. You handle failures individually.
Use this when children are independent. Processing multiple items where one failure should not stop the others. Multiple network calls where partial success is acceptable.
supervisorScope still waits for all children. It still cancels children when the scope is cancelled. But failure does not propagate.
Always launch coroutines in a scope.
In Android, use viewModelScope or lifecycleScope. They cancel when the ViewModel or lifecycle ends.
In libraries, accept a scope from the caller. Let them control the lifetime.
In tests, use runTest which provides a test scope.
Never launch coroutines into the void. Always have a scope. Always have a parent.
This discipline makes concurrent code predictable and safe.
Let us summarize.
Structured concurrency creates parent-child relationships between coroutines.
The scope defines the lifetime. Children cannot outlive their scope.
Cancellation propagates down. Cancel a parent, children cancel.
Failure propagates up. A child failure cancels siblings and fails the parent.
coroutineScope groups operations with shared fate.
supervisorScope allows independent child failures.
Structure makes concurrency manageable.