使用DispatchGroup时异步调用阻塞主线程



我正在尝试从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()而不是通知,因为我需要此函数只有在加载了attenders集合之后才能访问子集合和文档,因为我对子集合和文档需要这些值。我在网上读了很多答案,大多数人在这种情况下都使用group.wait(),但由于某种原因,如果不锁定和冻结应用程序,我就无法让它为我工作。

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

不管怎样,我可能倾向于将其分解为单独的函数,因为它有点多毛,但想法是一样的。

最新更新