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

如何终止正在进行的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 08:25:07