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

DispatchGroup结合forEach循环时Firestore函数未调用的问题

问题分析与修复方案

核心问题

  1. 死锁阻塞:你用group.wait()同步阻塞了当前线程,如果这个函数在主线程调用,Firestore的回调任务会因主线程被卡死无法执行,导致group.leave()永远触发不了,wait就一直卡住。
  2. 错误分支遗漏leave:Firestore请求出错时,你直接调用completion返回,但没执行group.leave(),DispatchGroup会一直等这个任务的leave信号,同样造成阻塞。
  3. 线程不安全:多个异步线程同时对plays数组执行append,会引发数据竞争,导致数组数据损坏或崩溃。

修复后的代码

func getUserGlobalPlays(username: String, fixtureIDs: [Int], completion: @escaping (Result<[UserPlays]?, Error>) -> Void) {    
    let chunkedArray = fixtureIDs.chunked(into: 10)
    var plays: [UserPlays] = []
    let group = DispatchGroup()
    // 串行队列保证数组操作的线程安全
    let playsQueue = DispatchQueue(label: "com.yourApp.playsQueue")
    
    chunkedArray.forEach { ids in
        group.enter()
        print("entered")
        
        DispatchQueue.global().async { [weak self] in
            guard let self = self else {
                group.leave()
                return
            }
            
            self.db.collection("Users").document("\(username)").collection("userPlays")
                .whereField("fixtureID", in: ids)
                .getDocuments { snapshot, error in
                    // 不管成功失败都执行leave,避免遗漏
                    defer {
                        group.leave()
                        print("leaving")
                    }
                    
                    guard let snapshot = snapshot, error == nil else {
                        completion(.failure(error!))
                        return
                    }
                    
                    // 用compactMap过滤无效数据,避免强制解包崩溃
                    let newPlays = snapshot.documents.compactMap { document -> UserPlays? in
                        guard let fixtureIDx = document["fixtureID"] as? Int,
                              let choice = document["userChoice"] as? Int else {
                            return nil
                        }
                        return UserPlays(fixtureID: fixtureIDx, userChoice: choice)
                    }
                    
                    // 串行队列同步写入数组,避免多线程竞争
                    playsQueue.sync {
                        plays.append(contentsOf: newPlays)
                    }
                }
        }
    }
    
    // 用notify替代wait,异步等待所有任务完成,避免阻塞线程
    group.notify(queue: .main) {
        print(plays.count)
        completion(.success(plays))
    }
}

关键修改点

  • 替换group.wait()为group.notify(queue: .main):异步等待任务完成,不会阻塞当前线程,从根源避免死锁。
  • 新增defer { group.leave() }:不管请求成功还是失败,都会触发leave,保证DispatchGroup计数正确。
  • 加入playsQueue串行队列:所有对plays的修改都通过这个队列同步执行,解决多线程数据竞争问题。
  • 用compactMap替代强制解包:过滤掉格式错误的数据,避免因数据异常导致崩溃。
  • 补充self判空逻辑:防止self提前释放时遗漏leave信号。

内容的提问来源于stack exchange,提问作者Mbasa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 13:30:41