考虑您在问题中包含的例子:
func run() async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
for cook in staff.keys {
group.addTask { try await cook.handleShift() }
}
group.addTask {
// keep the restaurant going until closing time
try await Task.sleep(for: shiftDuration)
}
try await group.next()
// cancel all ongoing shifts
group.cancelAll()
}
}
您的解释是正确的,小组的任何一项任务首先完成,无论是"轮班结束"超时任务,还是任何一项(!)在handleShift
个电话中.只有当您假设handleShift
个呼叫都不会在shiftDuration
消失之前完成时,这才有意义.但做出任何此类假设都是轻率的.(此外,任何真正的企业都会在一名员工失业后立即关闭办公室.哈哈.)
恕我直言,这是一个非常糟糕的例子,我不会因此而失眠.你的判断是正确的.
如果我想要run
来(a)让所有厨师完成工作;和(b)取消轮班结束时任何未完成的工作,我个人不会将"轮班结束"纳入任务组.通常,这种"超时"模式将用于非 struct 化并发,一个任务用于主任务(即,所有厨师的工作),另一个是"超时"(即,轮班结束).由于我们使用的是非 struct 化并发,因此我们将用withTaskCancellationHandler
手动传播取消:
func run() async throws {
// Unstructured concurrency for all the cooks
let workTask = Task {
try await withThrowingTaskGroup(of: Void.self) { group in
for cook in staff.keys {
group.addTask { try await cook.handleShift() }
}
try await group.waitForAll()
print("all work done")
}
}
// Unstructured concurrency for the end of the shift
let cancelTask = Task {
try await Task.sleep(for: shiftDuration) // keep the restaurant going until closing time
print("ending shift")
workTask.cancel() // at the end of the shift, tell the cooks to stop working
}
// And because we're using unstructured concurrency, we need to manually handle
// cancelation of `run`, itself.
try await withTaskCancellationHandler {
_ = try await workTask.value // perform all the work of `handleShift` for all the cooks
cancelTask.cancel() // if all the work finished, then `cancelTask` is no longer needed and should be canceled, itself; obviously, if the timeout `Task` ended up getting invoked (i.e., work was still in progress at the end of the shift), then we will not reach this line because `workTask` will throw `CancellationError`
} onCancel: {
cancelTask.cancel() // if `run` was canceled, though (e.g., there was a fire and the restaurant needed to be closed prematurely; lol), then just clean up
workTask.cancel()
}
}
或者,您可以使用他们的模式保持 struct 化并发,但将所有厨师的工作包装在组中的组中:
func run() async throws {
try await withThrowingTaskGroup(of: Void.self) { [staff, shiftDuration] group in
group.addTask {
try await withThrowingDiscardingTaskGroup { group in // or `withThrowingTaskGroup(of: Void.self)` in older versions
for cook in staff.keys {
group.addTask { try await cook.handleShift() }
}
}
print("all work done")
}
group.addTask {
try await Task.sleep(for: shiftDuration) // keep the restaurant going until closing time
print("ending shift")
}
try await group.next()
group.cancelAll()
}
}
这两个都有点笨拙(尤其是对于WWDC观众来说),所以我可以理解为什么苹果过度简化了他们的例子(但以牺牲他们据称试图解决的业务问题的正确性为代价).我怀疑他们只是想说明你可以取消任务组,并且不想陷入这样的困境.但这两个修改后的片段是常见"超时"模式的示例.这可能是他们试图说明的那种业务问题的更正确的实现.