Cómo utilizar Task Group
Libranner Santos
17 mayo, 20233min de lectura
En un artículo anterior, vimos cómo podemos usar paralelización con async let. Sin embargo, esta técnica tiene una limitación importante: solo es útil cuando sabemos en tiempo de desarrollo cuánta concurrencia necesitamos. Es decir, la cantidad de procesos concurrentes es estática.
Para casos donde la cantidad de procesos concurrentes se determine en tiempo de ejecución, podemos usar TaskGroup
. Estos contienen grupos de tareas asíncronas que se crean dinámicamente.
Para crear un TaskGroup
, utilizamos withThrowingTaskGroup(of:returning:body:)
:
await withThrowingTaskGroup(of: String.self,returning: [String]) { group in...}
Con este código creamos un TaskGroup
, pasando los siguientes parámetros:
of
indica qué tipo de datos manejarán las tareas hijas.returning
indica qué tipo de datos se retornará una vez ejecutadas todas las tareas del grupo.operation
, en este caso, usamos trailing closure, por lo que no es necesario indicar el nombre del último parámetro. Dentro de este closure, tenemosgroup
, que es el objeto que utilizaremos para agregar las tareas hijas.
Agregar Tasks
Para agregar tareas a nuestro TaskGroup
usamos el siguiente código:
await withThrowingTaskGroup(of: Void.self) { group ingroup.addTask(priority: .high) {try await fetch()}}
UsandoaddTask(priority:operation:)
podemos pasar la prioridad opcionalmente. Si no se especifica, hereda la prioridad del contexto. También pasamos un bloque de código donde se encuentra la tarea que queremos agregar.
Existe una alternativa a este método addTaskUnlessCancelled(priority:operation:)
. La única diferencia es que con este la tarea solo se agregará si el TaskGroup
no ha sido cancelado previamente.
Cómo cancelar Tasks dentro de un TaskGroup
TaskGroup
tiene un método para cancelar las tareas que no se han ejecutado cancelAll()
.
await withThrowingTaskGroup(of: Void.self) { group ingroup.addTask {await fetch()}group.addTask {await update()}if condition {group.cancelAll()}}
Observa que en este ejemplo pasamos Void.self
al parámetro of
, por lo que no retornaremos nada.
En este caso estamos evaluando condition
y si es true
ejecutamos cancelAll()
. Sin embargo, si alguna de las tareas agregadas previamente ha terminado, podremos ver el resultado, ya que solo se cancelan las tareas pendientes.
También, si una de las tareas hijas produce un error, las demás tareas faltantes serán canceladas.
Otra forma de que se pueda producir una cancelación es si la tarea padre del TaskGroup
es cancelada.
Lidiando con las respuestas en un TaskGroup
try await withThrowingTaskGroup(of: String.self) { group ingroup.addTask {await fetch()}group.addTask {await pull()}if condition {group.cancelAll()}for try await result in group {print(result)}}
En este código creamos dos tareas hijas que retornan un String
utilizando las funciones asíncronas fetch()
y pull()
. Sin embargo, no queremos esperar a que ambas terminen para imprimir los valores retornados. En este caso, podemos usar for await
que itera sobre los resultados de group
. Esto es posible gracias a que group
conforma a AsyncSequence
e implementa next()
.
Cosas a tomar en cuenta
- Si mientras trabajas en estas tareas te encuentras con una excepción en una de ellas, se cancelarán y detendrán automáticamente todas las tareas restantes en el grupo.
- Todas las tareas dentro dentro de un grupo deben retornar el mismo tipo de datos.
- A diferencia de
Task
yasync let
, podemos trabajar con los resultados deTaskGroup
sin orden definido, mientras van terminado, para esto utilizamos.next()
ofor await
.