/*:#localized(key: "CancellationStopsConsumption")
 # Cancellation stops consumption

 In the chapter [Tasks and Cancellation](Tasks%20and%20Cancellation.playgroundchapter/Overview) you saw that cancelling a task is a *request*, cooperatively honored.
 Async sequences honor it for you: **cancel the task that's doing the `for await`,
 and the loop stops at its next suspension point.**
 */
//#-hidden-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

// rooftopReadings() from the previous page, repeated so this page runs on its own.
func rooftopReadings() -> AsyncStream<Double> {
    AsyncStream { continuation in
        let producer = Task {
            while !Task.isCancelled {
                try? await Task.sleep(for: .milliseconds(300))
                continuation.yield(Double.random(in: 18...26))
            }
        }

        continuation.onTermination = { reason in
            producer.cancel()
            switch reason {
            case .cancelled:
                print("🧹 Consumer left: sensor powered down")
            case .finished:
                print("🧹 Stream finished: sensor powered down")
            @unknown default:
                break
            }
        }
    }
}
//#-end-hidden-code
//#-editable-code
let consumer = Task {
    for await celsius in rooftopReadings() {
        print("🌡️ \(celsius.rounded())°C")
    }
    print("Consumer loop ended")
}

// Let a few readings through, then pull the plug.
Task {
    try? await Task.sleep(for: .seconds(1))
    print("⏹️ Cancelling the consumer")
    consumer.cancel()
}

// Wait for the consumer to unwind (and its cleanup to run), then stop the page.
Task {
    _ = await consumer.value
    PlaygroundPage.current.finishExecution()
}
//#-end-editable-code
/*:#localized(key: "CancellationStopsConsumption-2")
 **Watch it run:** readings print for about a second. Then `Cancelling the consumer`
 appears; the `for await` loop ends the next time it would have awaited a value,
 `onTermination` fires with `.cancelled`, and you see the sensor power down. You
 didn't sprinkle `Task.isCancelled` checks through the loop: awaiting the next
 element *is* the cancellation check. That's the payoff of building on structured
 concurrency: stop the task, and consumption unwinds cleanly.

 There are two ways a consumer bows out, and both trigger the same cleanup:

 * **`break`** (or `return`, or the loop reaching a natural end): you're done reading.
 * **Cancelling the task**: someone upstream decided the work is no longer needed.

 [Previous](@previous)  |  page 5 of 7  |  [Next: When a Stream Can Fail](@next)
 */
