You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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))]
分析与解答

你的理解没有偏差,问题出在代码的两处细节处理上:

  1. try? await Task.sleep屏蔽了取消信号
    Task.sleep会主动检查当前Task的取消状态,一旦检测到取消就会抛出CancellationError。但你用try?捕获并忽略了这个错误,导致循环不会因取消而提前终止。父Task被取消后,Task.sleep本应抛出错误跳出循环,却被try?强制继续执行后续迭代,使得addTaskUnlessCancelled仍能继续添加任务。

  2. 未在循环迭代开头主动检查取消状态
    即便修复了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:43:10