【问题标题】:Async call blocking main thread when using DispatchGroup使用 DispatchGroup 时异步调用阻塞主线程
【发布时间】:2021-03-26 16:27:58
【问题描述】:

我正在尝试从 FireStore 数据库中获取文档。在继续执行我的功能之前,我需要加载这些文档。以下是参考代码:

调用 FireStore 服务函数的视图控制器:

    let service = FirestoreServices()
    service.get(cottage: "test123456789") { model in
        
        nextViewController.cottageModel = model!
        self.present(nextViewController, animated:true, completion:nil)
        
    }

正在调用的 FireStore 服务方法:

func get(cottage: String, completionHandler: @escaping (CottageTrip?) -> ()) {
    
    //get a reference to the firestore
    let db = Firestore.firestore()
    
    //get the references to the collections
    let cottageRef = db.collection("cottages").document(cottage)
    
    //get the fields from the initial document and load them into the cottage model
    cottageRef.getDocument { (document, error) in
        if let document = document, document.exists {
            
            //create the cottage model to return
            let cottageModel: CottageTrip = CottageTrip()
            
            //get the subcollections of this document
            let attendeesCollection = cottageRef.collection("attendees")
            //other collections
            
            //here I get all info from initial document and store it in the model
            
            let group = DispatchGroup()
            
            print("here")
            
            group.enter()
            
            //get the attendees
            DispatchQueue.global(qos: .userInitiated).async {
                
                attendeesCollection.getDocuments() { (querySnapshot, err) in
                    print("here2")
                    if let err = err {
                            print("Error getting documents: \(err)")
                    } else {
                        //get data
                    }
                    group.leave()
                }
                
            }
            
            print("after async call")
            
            //wait here until the attendees list is built
            group.wait()
            
            print("after wait")
            
            //create the cars
            carsCollection.getDocuments() { (querySnapshot, err) in
                print("in car collection get doc call")
                if let err = err {
                        print("Error getting documents: \(err)")
                } else {
                    //get car data
                    }
                }
            }
            
            //this is where she should block until all previous get document operations complete
            
            completionHandler(cottageModel)
            
        } else {
            print("Document does not exist")
            completionHandler(nil)
        }
        
    }
    
}

我意识到print("here2") 永远不会打印,所以它似乎阻塞在group.wait() 上。我需要使用group.wait() 而不是通知,因为我需要此函数仅在加载参加者集合后才能访问子集合和文档,因为我需要这些值用于子集合和文档。我在网上阅读了很多答案,大多数人在这种情况下使用group.wait(),但由于某种原因,如果不锁定和冻结应用程序,我无法让它为我工作。

【问题讨论】:

  • 你调用 wait() 它会阻塞你的主线程,直到 leave() 被调用。但它永远不会被调用,因为 getDocuments() 的完成闭包也需要在主线程上运行。你有一个僵局。
  • 我看不出使用基于您提供的代码的调度组有任何意义,因为似乎为创建山寨模型而执行的所有任务都已经是串行的。首先获取文档,然后查询子集合,然后执行x,然后执行y。所有这些都已经是串行的,所以只需像你一样嵌套它们,如果在任何时候出现故障(如缺少文档或网络错误),请在完成处理程序中返回 nil 并继续。
  • @bsod 这两个getDocuments 调用是异步的,所以如果你想让它们同时运行,你需要让组知道它们什么时候完成。但是,如果您需要一个结果用于另一个,那么您是正确的,您可以通过嵌套它们一个接一个地运行,从而消除对组的需要。

标签: ios swift firebase google-cloud-firestore grand-central-dispatch


【解决方案1】:

正如 algrid 指出的那样,您遇到了死锁,因为您正在等待主线程,Firestore 需要调用它的闭包。

作为一般规则,避免调用wait,您就不会死锁。使用notify,然后在notify 闭包中调用你的闭包。

因此,例如,假设您不需要来自attendees 的结果来查询cars,您可以只使用notify 调度组模式,例如

func get(cottage: String, completionHandler: @escaping (CottageTrip?) -> Void) {
    let db = Firestore.firestore()
    let cottageRef = db.collection("cottages").document(cottage)

    cottageRef.getDocument { document, error in
        guard let document = document, document.exists else {
            print("Document does not exist")
            completionHandler(nil)
            return
        }

        let cottageModel = CottageTrip()

        let attendeesCollection = cottageRef.collection("attendees")
        let carsCollection = cottageRef.collection("cars")

        let group = DispatchGroup()

        group.enter()
        attendeesCollection.getDocuments() { querySnapshot, err in
            defer { group.leave() }

            ...
        }

        group.enter()
        carsCollection.getDocuments() { querySnapshot, err in
            defer { group.leave() }

            ...
        }

        group.notify(queue: .main) {
            completionHandler(cottageModel)
        }
    }
}

另外,顺便说一句,但您不必将任何内容分派到全局队列,因为这些方法已经是异步的。


如果您需要一个结果来启动下一个,您可以嵌套它们。这会更慢(因为你放大了网络延迟效应),但也完全消除了对组的需要:

func get(cottage: String, completionHandler: @escaping (CottageTrip?) -> Void) {
    let db = Firestore.firestore()
    let cottageRef = db.collection("cottages").document(cottage)

    cottageRef.getDocument { document, error in
        guard let document = document, document.exists else {
            print("Document does not exist")
            completionHandler(nil)
            return
        }

        let cottageModel = CottageTrip()

        let attendeesCollection = cottageRef.collection("attendees")
        let carsCollection = cottageRef.collection("cars")

        attendeesCollection.getDocuments() { querySnapshot, err in
            ...

            carsCollection.getDocuments() { querySnapshot, err in
                ...

                completionHandler(cottageModel)

            }
        }
    }
}

无论哪种方式,我可能倾向于将其分解为单独的函数,因为它有点毛茸茸,但想法是一样的。

【讨论】:

    猜你喜欢
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    • 2021-12-12
    • 1970-01-01
    • 1970-01-01
    • 2013-11-16
    • 2016-07-14
    • 1970-01-01
    相关资源
    最近更新 更多