Despachar un closure con async lo encola y retorna de inmediato. El hilo que hizo el llamado no espera a que el closure se ejecute — sigue corriendo el código que viene después sin bloquearse.
let serialQueue = DispatchQueue(label: "dev.goyes.serial")
print("Antes")
serialQueue.async {
print("Dentro del closure")
}
print("Después")
Lo único garantizado en este código es que "Antes" se imprime antes que cualquiera de los otros dos: ocurre de forma síncrona, antes del llamado a async. El orden entre "Después" y "Dentro del closure" no está garantizado — async retorna inmediatamente, así que "Después" casi siempre se imprime primero, pero nada en la API lo asegura.
La cola sigue siendo serial: el orden entre tareas sí está garantizado
async cambia si el llamador espera, no cambia cómo procesa la cola las tareas. Una cola serial sigue ejecutando una tarea a la vez, en el orden en que se despacharon (FIFO), sin importar si se despacharon con sync o async.
let serialQueue = DispatchQueue(label: "dev.goyes.serial")
for i in 1...3 {
serialQueue.async {
print("Tarea \(i)")
}
}
print("Las tres tareas ya se encolaron")
// Siempre imprime, en este orden relativo:
// Tarea 1
// Tarea 2
// Tarea 3
// ("Las tres tareas ya se encolaron" puede aparecer en cualquier punto, antes o entre ellas)
El orden entre "Tarea 1", "Tarea 2" y "Tarea 3" está garantizado por el FIFO de la cola. Lo que no está garantizado es cuándo, respecto a ellas, se imprime "Las tres tareas ya se encolaron" — eso depende del scheduler, no de la cola.
async reentrante no produce deadlock
A diferencia de sync, llamar async sobre la misma cola serial en la que el código ya se está ejecutando es seguro. async nunca bloquea, así que solo agrega el closure al final de la cola y continúa.
let serialQueue = DispatchQueue(label: "dev.goyes.serial")
serialQueue.async {
print("Tarea A")
serialQueue.async { // Seguro — no hay deadlock
print("Tarea B")
}
print("A sigue ejecutando")
}
// Tarea A
// A sigue ejecutando
// Tarea B
La tarea A termina de ejecutarse por completo (incluyendo el print después del async anidado) antes de que la cola pase a la tarea B, porque B se encoló al final, detrás de lo que faltaba de A.
Por qué Cache.write usa async
En el artículo anterior, write(_:forKey:) despacha con async precisamente por esto: quien escribe en la caché no necesita esperar a que la escritura se aplique para seguir ejecutando. Lo único que importa es que, cuando se llegue a leer con sync, todas las escrituras encoladas antes ya se hayan procesado — y eso lo garantiza el FIFO de la cola serial, no el hecho de que el llamador haya esperado.
Lo que viene
Falta ver qué cambia cuando la cola, en lugar de serial, es concurrente: varias tareas pueden correr al mismo tiempo, y el FIFO deja de implicar "una a la vez". Eso es lo que cubren los siguientes dos artículos.
Top comments (0)