DEV Community

GoyesDev
GoyesDev

Posted on

[GCD] DQ Serial: ejecución asíncrona

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")
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.


Bibliografía

Top comments (0)