简体   繁体   中英

Async call blocking main thread when using DispatchGroup

I am trying to get documents from a FireStore database. I need these documents to be loaded before moving forward in my function. Here is the code for reference:

The view controller calling the FireStore service functions:

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

The FireStore service method being called:

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)
        }
        
    }
    
}

I am realizing that the print("here2") never prints so it seems like it blocks on the group.wait() . I need to use group.wait() rather than a notify because I need this function to access subcollections and documents only after the attendees collection is loaded as I need these values for the subcollections and documents. I have read a lot of answers online and most people use group.wait() in this scenario but for some reason I can not get it to work for me without locking and freezing the application.

As algrid pointed out, you have a deadlock because you are waiting on the main thread, which Firestore needs to call its closures.

As a general rule, avoid calling wait and you will not deadlock. Use notify , and just call your closure inside that notify closure.

So, for example, assuming that you do not need the results from attendees in order to query the cars , you can just use a notify dispatch group pattern, eg

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)
        }
    }
}

Also, as an aside, but you do not have to dispatch anything to a global queue, as these methods are already asynchronous.


If you needed the result from one in order to initiate the next, you can just nest them. This will be slower (because you magnify the network latency effect), but also eliminates the need for the group at all:

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)

            }
        }
    }
}

Either way, I might be inclined to break this up into separate functions, as it is a little hairy, but the idea would be the same.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM