/*:#localized(key: "StructuredGuarantee")
 # The structured guarantee

 Task groups are **structured concurrency**, and that word "structured" carries a
 promise worth saying out loud:

 **The `withTaskGroup` scope will not return until every child task has finished
 or been cancelled.** No child can outlive the group. When control passes the
 closing brace, there are zero leftover tasks running in the shadows.

 This is why you never have to track child tasks by hand, cancel them yourself, or
 worry that one escaped and is still burning CPU after you've moved on. The
 *structure* of your code, the group's braces, bounds the *lifetime* of the work.
 Even in the throwing example on the previous page, the group didn't return until
 the cancelled siblings had actually stopped.

 Cancellation flows the same way it did in the chapter [Tasks and Cancellation](Tasks%20and%20Cancellation.playgroundchapter/Overview): **down the tree**. Cancel
 the task that owns the group, and every child is marked cancelled too. And a
 child is just an ordinary task, so it observes cancellation the same way:
 `Task.sleep` throws `CancellationError`, and you can check `Task.isCancelled` in a
 long loop to bail out early.
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

func processFrame(_ index: Int) async -> Double {
    try? await Task.sleep(for: .milliseconds(500))
    return Double(index) * 1.5
}

Task {
    let stack = Task {
        await withTaskGroup(of: Double.self) { group in
            for index in 1...100 {
                group.addTask { await processFrame(index) }
            }
            var sum = 0.0
            for await brightness in group {
                sum += brightness
            }
            return sum
        }
    }

    // Change our mind after a moment and cancel the whole stack.
    try? await Task.sleep(for: .milliseconds(200))
    stack.cancel()

    let total = await stack.value
    print("Cancelled early; partial total = \(total)")
    PlaygroundPage.current.finishExecution()
}
//#-end-editable-code
/*:#localized(key: "StructuredGuarantee-2")
 **Watch it run:** cancelling `stack` propagates into the group and down to all
 100 children. Their `Task.sleep` calls throw and unwind (here the `try?`
 swallows the error and yields `nil`, so those frames contribute their fallback),
 and the group still returns cleanly, because the guarantee holds even when the
 answer is "stop." Nothing leaks.

 [Previous](@previous)  |  page 7 of 9  |  [Next: Capping How Many Run at Once](@next)
 */
