如何终止正在进行的Dispatch Group执行并启动新任务?
分页API重复请求问题的修复方案
现有代码的核心问题
- 无请求取消机制:一旦发起分页请求链就无法中断,用户重复进入页面时,旧请求仍在执行,新请求又启动,导致多任务并行。
- 全局状态未拦截新任务:
storeAllData直接设置GlobalVariable.isFetchingData = true,但未提前检查当前是否正在请求,完全无法阻止新任务启动。 - dispatchGroup使用错误:
fetchAllScore中的dispatchGroup.wait()会阻塞后台队列,多任务共用同一个group会导致等待逻辑混乱;部分分支未调用leave(),可能造成死锁。 - 递归分页耦合过强:
fetchPaginatedData完成一页后递归调用自身,启动后无法中断整个请求链。
修复方案:添加请求取消+任务拦截
第一步:给网络请求增加取消能力
修改MatchesNetworkManager的getAllScore方法,让它返回对应的URLSessionTask,这样就能通过task.cancel()终止正在进行的请求。
第二步:重构页面任务控制逻辑
在页面类中维护当前请求任务列表,以及本地的isFetching状态(替换全局变量,避免多页面干扰)。
重构后的代码示例
// 页面类新增属性 private var currentFetchTasks: [URLSessionTask] = [] private var isFetching = false func storeAllData(matchID: Int64) { // 先取消所有正在执行的请求 cancelCurrentFetchTasks() isFetching = true let dbManager = DbManager.shared let lastScoreID = dbManager.getLastBallForCurrentMatch(matchID: matchID)?.id ?? 0 fetchAllScore(scoreID: lastScoreID, matchID: matchID) } private func cancelCurrentFetchTasks() { currentFetchTasks.forEach { $0.cancel() } currentFetchTasks.removeAll() isFetching = false } func fetchAllScore(scoreID: Int = 0, matchID: Int64) { backgroundQueue.async { [weak self] in guard let self = self else { return } autoreleasepool { self.fetchPaginatedData(matchID: Int(matchID), scoreID: scoreID, page: 1) } } } func fetchPaginatedData(matchID: Int, scoreID: Int = 0, page: Int) { // 已取消请求则直接返回,避免无效执行 guard isFetching else { return } // 发起请求并保存任务引用 let task = MatchesNetworkManager.sharedInstance.getAllScore(matchID: Int64(matchID), scoreID: Int64(scoreID), page: Int64(page)) { [weak self] (allScore, isSuccess, errorStr) in guard let self = self else { return } // 任务完成后从列表移除 if let index = self.currentFetchTasks.firstIndex(where: { $0 === task }) { self.currentFetchTasks.remove(at: index) } if isSuccess { guard let scores = allScore?.match?.scores as? [Scores] else { self.updateScoreView() self.isFetching = false return } // 插入数据库操作 self.backgroundQueue.async { self.dbManager.insertRecords(matchID: matchID, records: scores) DispatchQueue.main.async { if page == 1 { self.updateScoreView() } let currentPage = allScore?.page_num ?? 1 let totalPages = allScore?.total_pages ?? 1 // 还有下一页且未取消请求时,继续发起下一页请求 if currentPage < totalPages && self.isFetching { self.fetchPaginatedData(matchID: matchID, page: currentPage + 1) } else { // 请求全部完成或已取消 self.isFetching = false print("请求全部完成或已取消") NotificationCenter.default.post(name: .refreshMarketView, object: nil) } } } } else { print("API请求出错: \(errorStr)") self.updateScoreView() self.isFetching = false } } currentFetchTasks.append(task) }
额外说明
- 网络层需配合修改:必须确保
getAllScore方法返回URLSessionTask,否则无法实现请求取消功能,若之前的网络层未暴露该引用,需先调整网络层代码。 - 本地状态替代全局变量:页面自行维护
isFetching状态,避免多个页面操作互相干扰。 - 移除dispatchGroup:分页请求为串行触发(完成一页再请求下一页),通过
isFetching状态即可控制流程启停,无需使用dispatchGroup,避免引发死锁等问题。 - 递归前检查状态:每次发起下一页请求前,先确认
isFetching状态,避免执行已取消的请求。
内容的提问来源于stack exchange,提问作者NickCoder
相关产品推荐
相关产品推荐

