Swift中如何获取TaskGroup首个完成任务结果并取消其余任务
问题原因
Swift 结构化并发的任务取消是协作式的:系统不会强制终止正在运行的任务,只会为被取消的任务打上取消标记,必须由任务内部的代码主动检查取消状态,才能及时退出执行。你之前的实现里没有任何取消检查逻辑,所以哪怕父TaskGroup调用了cancelAll(),正在跑while循环的生成任务也会一直执行到找到有效谜题才退出,自然没法及时终止。
修复方案
只需要在三个关键位置加入取消检查逻辑即可:
generate()方法的while循环每次迭代开头,检查任务是否被取消- 所有嵌套的
withTaskGroup结果遍历逻辑中,检查到取消时立刻终止子任务、退出循环 - 子任务执行入口处加入取消检查,避免无意义的计算
修改后的核心代码
1. 改造generate()方法
static func generate() async throws -> Self { var outerLetters: [String]? var middleLetters: [String]? var centerLetter: [String]? var words: [String]? var count: Int = 0 while true { // 每次循环前先检查取消状态,被取消则直接抛出错误退出 try Task.checkCancellation() count += 1 let outer = ContentView.ViewModel.randomizeAvailableLetters(tileArraySize: 16, from: ContentView.ViewModel.weightedOuter) let middle = ContentView.ViewModel.randomizeAvailableLetters(tileArraySize: 8, from: ContentView.ViewModel.weightedMiddle) let center = ContentView.ViewModel.randomizeAvailableLetters(tileArraySize: 1) let possibleWords: Set<String> = Set(await Puzzle.getWords(outer: outer, middle: middle, center: center)) var actual = [String]() for word in possibleWords { if ContentView.ViewModel.wordList.contains(word) { actual.append(word) } } if actual.count >= 5 { outerLetters = outer middleLetters = middle centerLetter = center words = actual print("Count: \(count)") break } } guard let outer = outerLetters, let middle = middleLetters, let center = centerLetter, let words = words else { fatalError("You should not be here...") } return Puzzle(outerLetters: outer, middleLetters: middle, centerLetter: center, words: words) }
2. 改造内部getWords方法,给嵌套任务组加取消检查
static func getWords(outer: [String], middle: [String], center: [String]) async -> [String] { @Sendable func getOuter(with middle: [String], outer: [String]) async -> [String] { var possibleWords = [String]() let result = await withTaskGroup(of: [String].self) { group -> [String] in var words = [String]() for i in 0..<middle.count { group.addTask { try? Task.checkCancellation() return getMiddle(with: middle, outer: outer) } } for await word in group { // 遍历结果时检查取消,发现取消就清空剩余子任务直接返回 if Task.isCancelled { group.cancelAll() break } words.append(contentsOf: word) } return words } possibleWords.append(contentsOf: result) return possibleWords } @Sendable func getMiddle(with middle: [String], outer: [String]) -> [String] { var possibleWords = [String]() // 如果这里有长循环构造单词的逻辑,同样可以在循环内加Task.isCancelled判断提前退出 return possibleWords } return await withTaskGroup(of: [String].self) { group in var possibleWords = [String]() for i in 0..<outer.count { group.addTask { try? Task.checkCancellation() return await getOuter(with: middle, outer: outer) } } for await words in group { if Task.isCancelled { group.cancelAll() break } possibleWords.append(contentsOf: words) } return possibleWords } }
3. 改造multiGenerate方法,使用支持抛错的TaskGroup
func multiGenerate() async -> Puzzle { do { return try await withThrowingTaskGroup(of: Puzzle.self, body: { group in // 并发启动5个生成任务 for _ in 0..<5 { group.addTask { return try await Puzzle.generate() } } // 拿到第一个生成成功的谜题就取消所有剩余任务,直接返回结果 guard let firstPuzzle = try await group.next() else { fatalError("Puzzle generation failed") } group.cancelAll() return firstPuzzle }) } catch { // 捕获任务取消抛出的CancellationError,兜底重试即可 return await multiGenerate() } }
额外性能优化建议
你现在单轮生成需要遍历上千次的核心原因是单词匹配逻辑效率太低:当前用数组的contains做匹配是O(n)复杂度,把ContentView.ViewModel.wordList提前转成Set<String>类型,再用集合的intersection方法直接求possibleWords和词表的交集,单轮匹配速度会提升几十上百倍,哪怕用2300词的小词表也能做到秒级生成,不需要靠并发堆任务提速。
内容的提问来源于stack exchange,提问作者forgot
相关产品推荐
相关产品推荐

