Changes in new versions / Swift 5.5

// *** before: ***
let group = DispatchGroup()
var first: Data?
var second: Data?

group.enter()
loadOld("a") { first = $0; group.leave() }
group.enter()
loadOld("b") { second = $0; group.leave() }

group.notify(queue: .main) { use(first, second) }
// two shared variables, two leaves that must not be missed, and no way to
// cancel the pair once it started

// *** in version 5.5: ***
func loadBoth() async throws -> (Data, Data) {
    async let first = load("a")        // both start right here
    async let second = load("b")
    return try await (first, second)   // and here we wait for both
}

// a task is the bridge from ordinary code into the asynchronous world
let task = Task {
    let pair = try await loadBoth()
    print(pair)
}

task.cancel()   // the cancellation reaches the children of the task as well

// a child task cannot outlive its parent: that is what "structured" means