swift - Orden de ejecución asíncrono de cierre rápido
asynchronous closures (1)
Un par de observaciones:
-
Siempre ejecutará lo que está en 1 antes de 2. La única forma en que obtendría el comportamiento que describe es si está haciendo algo más dentro del ciclo for que es, en sí mismo, asíncrono. Y si ese fuera el caso, usaría un grupo de despacho para resolverlo (o refactorizaría el código para manejar el patrón asincrónico). Pero sin ver qué hay en ese bucle for, es difícil comentar más. El código en la pregunta, solo, no debe manifestar el problema que usted describe. Tiene que ser algo más.
-
Sin relación, debe tener en cuenta que es un poco peligroso actualizar los objetos del modelo dentro de su ejecución asincrónica para el bucle (suponiendo que se ejecute en un hilo de fondo). Es mucho más seguro actualizar una variable local, y luego pasarla de vuelta a través del controlador de finalización, y dejar que la persona que llama se encargue de enviar tanto la actualización del modelo como la UI a la cola principal.
-
En los comentarios, mencionas que en el bucle
for
estás haciendo algo asíncrono y algo que debes completar antes de que se llame a completeHandler. Por lo tanto, usaría un grupo de despacho para asegurarse de que esto suceda solo después de que se hayan realizado todas las tareas asincrónicas. -
Tenga en cuenta que, dado que está haciendo algo asincrónico dentro del ciclo
for
, no solo necesita usar un grupo de despacho para activar la finalización de estas tareas asincrónicas, sino que probablemente también necesite crear su propia cola de sincronización (no debería mutar una matriz de múltiples hilos). Por lo tanto, puede crear una cola para esto.
Al unir todo esto, terminas con algo como:
func fetchMostRecent(completionHandler: ([TableItem]?) -> ()) {
addressBook.loadContacts { contacts, error in
var sections = [TableItem]()
let group = dispatch_group_create()
let syncQueue = dispatch_queue_create("com.domain.app.sections", nil)
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
dispatch_group_enter(group)
self.someAsynchronousMethod {
// handle contacts
dispatch_async(syncQueue) {
let something = ...
sections.append(something)
dispatch_group_leave(group)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
self.mostRecent = sections
completionHandler(sections)
}
} else {
completionHandler(nil)
}
}
}
Y
model.fetchMostRecent { sortedSections in
guard let sortedSections = sortedSections else {
// handle failure however appropriate for your app
return
}
// update some UI
self.state = State.Loaded(sortedSections)
self.tableView.reloadData()
}
O, en Swift 3:
func fetchMostRecent(completionHandler: @escaping ([TableItem]?) -> ()) {
addressBook.loadContacts { contacts, error in
var sections = [TableItem]()
let group = DispatchGroup()
let syncQueue = DispatchQueue(label: "com.domain.app.sections")
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
group.enter()
self.someAsynchronousMethod {
// handle contacts
syncQueue.async {
let something = ...
sections.append(something)
group.leave()
}
}
}
group.notify(queue: .main) {
self.mostRecent = sections
completionHandler(sections)
}
} else {
completionHandler(nil)
}
}
}
En mi modelo, tengo la función de recuperar datos que esperan un controlador de finalización como parámetro:
func fetchMostRecent(completion: (sortedSections: [TableItem]) -> ()) {
self.addressBook.loadContacts({
(contacts: [APContact]?, error: NSError?) in
// 1
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
// handle constacts
...
self.mostRecent.append(...)
}
}
// 2
completion(sortedSections: self.mostRecent)
})
}
Llama a otra función que realiza la carga asíncrona de contactos, a la que reenvío mi finalización
La llamada de
fetchMostRecent
con finalización se ve así:
model.fetchMostRecent({(sortedSections: [TableItem]) in
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.state = State.Loaded(sortedSections)
self.tableView.reloadData()
}
})
Esto a veces funciona, pero muy a menudo el orden de ejecución no es el que esperaba.
El problema es que, a veces, la ejecución
completion()
en
// 2
se ejecuta antes del alcance de
if
en
// 1
se terminó.
¿Porqué es eso?
¿Cómo puedo asegurarme de que la ejecución de
// 2
se inicie después de
// 1
?