Framework: Dispatch
Un DispatchGroup en GCD sirve para sincronizar varias tareas concurrentes y ejecutar algo cuando todas hayan terminado.
En el siguiente ejemplo se usa notify(queue:work:) para sincronizar las tareas despachadas con queue.async. Observar que debo marcar explícitamente el ingreso al grupo antes de llamar queue.async con group.enter(). Además, una vez que haya terminado el trabajo asíncrono, debo llamar group.leave() dentro de queue.async.
import Foundation
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)
for i in 1...3 {
// Inicio de la tarea
group.enter()
queue.async {
print("Iniciando tarea \(i)")
// Simulando trabajo
Thread.sleep(forTimeInterval: Double(i))
print("Terminando tarea \(i)")
// Terminando la tarea
group.leave()
}
}
// Notificar cuando todas las tareas terminan
group.notify(queue: .main) {
print("Todas las tareasterminaron")
}
Marcar inicio y fin del bloque forma implícita
También se puede manejar el enter/leave de forma implícita con async(group:execute:), que implica que hay un enter() al principio del bloque del código y un leave() al final del bloque. Tener en cuenta que este llamado no sirve cuando se usan closures para reportar el fin de una tarea asíncrona. Por ejemplo:
import Foundation
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)
for i in 1...3 {
queue.async(group: group) {
print("Iniciando tarea \(i)")
// Simulando trabajo
Thread.sleep(forTimeInterval: Double(i))
print("Terminando tarea \(i)")
}
}
// Notificar cuando todas las tareas terminan
group.notify(queue: .main) {
print("Todas las tareasterminaron")
}
Sincronización bloqueante
Aparte de notify, también puedo usar wait() que bloquea el hilo actual.
// Notificar cuando todas las tareas terminan sin bloquear
group.notify(queue: .main) {
print("Todas las tareasterminaron")
}
// Bloquea hasta que todas las tareas terminan
group.wait()
print("Se imprime después de sincronizar")
Balance estricto enter/leave
Cada group.enter() debe tener su correspondiente group.leave(). Si se llama enter() más veces que leave(), el grupo nunca podrá sincronizarse (se queda colgado). Por otro lado, si llamo más leave() que enter(), la app puede estallarse con un EXC_BAD_INSTRUCTION.
Enter antes de lanzar la tarea
El llamado al enter() debe ocurrir ANTES de iniciar la tarea asíncrona, no dentro.
La siguiente implementación está correcta:
group.enter()
queue.async {
// ...
group.leave()
}
Cubrir todos los caminos de ejecución
Si la tarea puede terminar por varias causas (éxito, error, cancelación, etc), hay que asegurarse de llamar leave() en todos los casos.
group.enter()
URLSession.shared.dataTask(with: url) { data, response, error in
// garantizar leave al salir, pase lo que pase
defer { group.leave() }
if let error = error {
print("Error:", error)
return
}
print("Descargado:", data?.count ?? 0)
}.resume()
Aquí defer en Swift es un patrón muy útil para no olvidar el leave().
Cola de sincronización de tareas
El parámetro queue en el group.notify(queue:) se refiere a qué cola va a ejecutar el bloque de código de sincronización cuando todas las tareas del grupo terminen.
group.notify(queue: .main) {
print("Todas listas en MAIN thread: \(Thread.current)")
// Aquí puedo actualizar UI
}
group.notify(queue: .global(qos: .userInitiated)) {
print("Todas listas en cola GLOBAL: \(Thread.current)")
// Aquí puedo seguir con trabajo en background
}
Si quiero actualizar la interfaz gráfica, el notify debe ir en .main. Si quiero seguir procesando datos, puedo usar otra cola global concurrente.
Top comments (0)