Direct Answer
Cancellation propagates automatically only within structured concurrency boundaries — TaskGroup.addTask and async let. Unstructured tasks created with Task { } or Task.detached { } form independent cancellation scopes and do not inherit cancellation from their creating context. The Swift language reference defines this behavior under structured concurrency guarantees; there is no separate "cancellation scoping API." Manual Task.isCancelled checks (or Task.checkCancellation()) are still required inside every child task at suspension points — cancellation is cooperative, not preemptive.
Confirmed Boundaries
| Context | Inherits Parent Cancellation | Notes |
TaskGroup.addTask { } | Yes | Automatic when group is cancelled |
async let | Yes | Bound to enclosing scope's lifetime |
Task { } (unstructured) | No | Independent scope; check Task.isCancelled manually |
Task.detached { } | No | Explicitly excluded by design |
AsyncStream / AsyncSequence | Producer side | Consumer cancellation terminates iteration, signalling producer |
What "Automatic" Actually Means
- Parent cancellation sets the child's
Task.isCancelled flag.
- Child must cooperatively observe it — typically at
await points (e.g., Task.sleep, I/O, Task.checkCancellation()).
- Blocking synchronous code in a child cannot be interrupted by cancellation.
- Grandchildren in nested
TaskGroups follow the same rule transitively.
Practical Verification
// Structured: cancellation propagates
let group = TaskGroup { group in
group.addTask {
// Example loop that cooperatively checks cancellation
while !Task.isCancelled {
await Task.sleep(seconds: 1)
}
}
}
// Cancel the group; children will see isCancelled == true
await group.cancelAll()
// Unstructured: NO propagation
let parent = Task {
let child = Task {
while !Task.isCancelled {
await Task.sleep(seconds: 1)
}
}
// Cancelling parent does not affect child unless it checks isCancelled
parent.cancel()
}
Specification Reference
Swift.org documentation: "Structured Concurrency" and "Task Cancellation" sections. The guarantees are part of the structured concurrency model (Swift 5.5+), not a standalone cancellation API. withTaskCancellationHandler adds cleanup hooks but does not alter propagation rules.
One Diagnostic Question
Are your child tasks launched via TaskGroup/async let (structured) or Task { }/Task.detached { } (unstructured)? The answer determines whether you need manual propagation code.