/*:#localized(key: "DispatchGroupFinishLine")
 # `DispatchGroup`: a finish line for many tasks

 A **`DispatchGroup`** is a counter that tracks a set of tasks so you can be
 notified when the whole set completes. You mark a task as joining the group
 with `enter()`, and mark it finished with `leave()`. When the number of
 `leave()` calls catches up to the number of `enter()` calls (the group is
 **empty**), the group fires.
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let group = DispatchGroup()
let barista = DispatchQueue(label: "barista", attributes: .concurrent)
let brewTimes = ["Latte": 0.8, "Cold brew": 0.3, "Espresso": 0.5]

for (drink, seconds) in brewTimes {
    group.enter()              // one task joins the group
    barista.async {
        Thread.sleep(forTimeInterval: seconds)
        print("✅ \(drink) ready")
        group.leave()          // this task is done
    }
}

group.notify(queue: .main) {   // runs when the group empties
    print("🧾 All drinks ready: hand the tray to the customer")
    PlaygroundPage.current.finishExecution()
}

print("📤 Batch sent to the counter; barista is working")
//#-end-editable-code
/*:#localized(key: "DispatchGroupFinishLine-2")
 `notify(queue:)` is the key line: it registers a block to run **later**, on the
 queue you name, the moment the group becomes empty. It does **not** block. Your
 code keeps flowing right past it.

 **Watch it run:** two things to notice. First, `📤 Batch sent...` prints before
 any drink; `notify` didn't block. Second, the drinks appear in **finish order**
 (cold brew, then espresso, then latte), *not* the order you started them,
 exactly the lesson from an earlier chapter. But the tray line always comes
 **last**, after all three, because the group waited for every `leave()`.
 That's the whole point: you get one clean "all done" callback no matter what
 order the tasks finish in.

 * callout(Try it): Swap the brew times so Latte is `0.1` and Cold brew is
 `0.9`. The drinks reorder, but does the tray line still land last?

 > When the work is a plain `async` closure, you can skip
 > the manual pair and let the queue balance it for you:
 > `barista.async(group: group) { ... }`. It's a **shortcut** that calls `enter()`
 > before your block and `leave()` after. Use the manual `enter()`/`leave()`
 > when the "done" moment is a *callback* that fires later (like a network
 > completion handler) where there's no single closure whose end means
 > "finished."

 [Previous](@previous)  |  page 3 of 8  |  [Next: wait(): The Blocking Alternative](@next)
 */
