/*:#localized(key: "CooperativeCancellation")
 # Cooperative cancellation

 Here's the part that surprises everyone. **Cancelling a task does not stop it.**

 When you call `task.cancel()`, Swift sets a flag: `isCancelled` becomes `true`
 for that task and all its children. That's *all* it does. It never yanks the
 CPU away, never throws into the middle of your loop, never forces a return.
 The running work keeps running until it voluntarily *checks* the flag and
 decides to stop. This is called **cooperative cancellation**: the system asks,
 and your code cooperates.

 So cancellable code has to *look*. There are three ways to look:

 * **`Task.isCancelled`**: a `Bool` you can read any time. Check it and return
 early.
 * **`try Task.checkCancellation()`**: throws `CancellationError` if
 cancelled, so one line both checks and bails out.
 * **`Task.sleep`**, already checks for you: if the task is cancelled while it
 sleeps, `Task.sleep` throws `CancellationError` instead of waiting out the
 clock.

 Let's build the real thing: a long-running job that we cancel partway through,
 and watch it stop *promptly* because it checks between each step. Picture the
 barista grinding beans batch by batch for a big catering order.
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let grind = Task { () -> String in
    for batch in 1...20 {
        try Task.checkCancellation()          // bail out if asked
        print("Grinding batch \(batch)...")
        try await Task.sleep(for: .milliseconds(250))
    }
    return "20 batches of ground coffee"
}

// A moment later, the catering order gets cancelled.
Task {
    try? await Task.sleep(for: .milliseconds(800))
    print(">>> Order cancelled")
    grind.cancel()

    do {
        let result = try await grind.value
        print("Finished: \(result)")
    } catch is CancellationError {
        print("Grinder stopped early: no wasted beans")
    }
    PlaygroundPage.current.finishExecution()
}
//#-end-editable-code
/*:#localized(key: "CooperativeCancellation-2")
 **Watch it run:** you'll see a few `Grinding batch...` lines (roughly three,
 given the timing), then `>>> Order cancelled`, then `Grinder stopped early...`.
 The grind loop was on batch four or five, but instead of grinding all twenty,
 it stopped within a single step of being cancelled. Two checks did the work:
 `Task.sleep` threw the instant `cancel()` landed mid-sleep, and
 `checkCancellation()` guards the top of each loop. Change *both* checks into
 comments and the loop grinds all twenty batches no matter when you cancel:
 proof that cancellation is a request, not a command.

 * callout(Try it): Change the cancel delay from `800` to `200`. How many
 batches grind before it stops?

 [Previous](@previous)  |  page 7 of 11  |  [Next: Cancelling Gracefully](@next)
 */
