Swift中TaskGroup未响应父Task取消的问题排查
问题
根据Apple官方文档及WWDC内容,Swift并发中取消Task时,子任务与TaskGroup会被隐式取消。但实际测试发现,取消承载TaskGroup的父Task后,TaskGroup并未识别到取消状态:示例代码中start函数启动一个Task,该Task创建TaskGroup并每2秒添加一个子任务,2秒后取消父Task,预期部分任务标记为未完成,但实际所有任务均执行完成,addTaskUnlessCancelled未按预期工作。
代码示例
public struct Thing { let completed: Bool let value: Int? } public struct TaskGroupCancellation { public mutating func start() async { let task = Task { let things = await withTaskGroup(of: Thing.self, returning: [Thing].self, body: { group in print("withTaskGroup start") var t = [Thing]() for i in 0..<3 { let isCancelled = group.isCancelled let added = group.addTaskUnlessCancelled { print("Adding value \(i) (canceled?: \(isCancelled))") return Thing(completed: true, value: i) } if !added { print("Marking value \(i) as cancelled") t.append(Thing(completed: false, value: nil)) } try? await Task.sleep(for: .seconds(2)) } for await thing in group { t.append(thing) } print("withTaskGroup end") return t }) print("Things: \(things)") } try? await Task.sleep(for: .seconds(2)) task.cancel() print("Cancelled") } }
运行输出
withTaskGroup start Adding value 0 (canceled?: false) Cancelled Adding value 2 (canceled?: false) Adding value 1 (canceled?: false) withTaskGroup end Things: [TaskGroupCancellation.Thing(completed: true, value: Optional(0)), TaskGroupCancellation.Thing(completed: true, value: Optional(2)), TaskGroupCancellation.Thing(completed: true, value: Optional(1))]
分析与解答
你的理解没有偏差,问题出在代码的两处细节处理上:
try? await Task.sleep屏蔽了取消信号Task.sleep会主动检查当前Task的取消状态,一旦检测到取消就会抛出CancellationError。但你用try?捕获并忽略了这个错误,导致循环不会因取消而提前终止。父Task被取消后,Task.sleep本应抛出错误跳出循环,却被try?强制继续执行后续迭代,使得addTaskUnlessCancelled仍能继续添加任务。未在循环迭代开头主动检查取消状态
即便修复了Task.sleep的错误处理,循环也需要在每次迭代开始时先判断group.isCancelled,如果已取消就直接终止后续操作。否则,取消信号传播到TaskGroup后,代码仍会执行到addTaskUnlessCancelled步骤,无法及时响应取消。
修复后的代码示例
public struct Thing { let completed: Bool let value: Int? } public struct TaskGroupCancellation { public mutating func start() async { let task = Task { let things = await withTaskGroup(of: Thing.self, returning: [Thing].self, body: { group in print("withTaskGroup start") var t = [Thing]() for i in 0..<3 { // 每次迭代先检查取消状态,提前终止无效操作 guard !group.isCancelled else { print("Marking value \(i) as cancelled") t.append(Thing(completed: false, value: nil)) continue } let isCancelled = group.isCancelled let added = group.addTaskUnlessCancelled { print("Adding value \(i) (canceled?: \(isCancelled))") return Thing(completed: true, value: i) } if !added { print("Marking value \(i) as cancelled") t.append(Thing(completed: false, value: nil)) } // 移除try?,让取消信号正常抛出以终止循环 try await Task.sleep(for: .seconds(2)) } for await thing in group { t.append(thing) } print("withTaskGroup end") return t }) print("Things: \(things)") } try? await Task.sleep(for: .seconds(2)) task.cancel() print("Cancelled") } }
修复后预期输出
withTaskGroup start Adding value 0 (canceled?: false) Cancelled Marking value 1 as cancelled Marking value 2 as cancelled withTaskGroup end Things: [TaskGroupCancellation.Thing(completed: true, value: Optional(0)), TaskGroupCancellation.Thing(completed: false, value: nil), TaskGroupCancellation.Thing(completed: false, value: nil)]
内容的提问来源于stack exchange,提问作者pmwood
相关产品推荐
相关产品推荐

