I see, so the asynchrony (and concurrency) model is meant to be used with effects, not just for calculation. That part of the decision has been eluding me for quite some time.
I still have some uneasy feelings about some actor optimization like combining partial tasks of the same executor or skipping an empty partial task if we're going for effectful programming. Though I'll need to think about it some more (and it's irrelevant here anyway).
But while significant, throwing errors isn't the only way in Swift to propagate errors. Even TaskGroup.add
, like many Cocoa imports, returns Bool
to indicate its success/failure. Not to mention failable initializers and other Cocoa imports that return nil
to indicate failure.
Plus, opting-out of cancellation is easier, too:
// Opt-out of cancellation
Task.withGroup {
while await group.get() { }
}
do {
async let foo = _
_ = await foo
}
Opting-in for child task requires you to throw a dummy error regardless of the success/failure condition:
// Opt-in for cancellation
Task.withGroup {
group.cancelAll()
}
try {
do {
async let foo = _
throw CancellationError()
}
} catch {
// Might actually succeed, but we want
// to cancel long-running unused tasks.
}