SC #11: Task Groups

Published: (January 19, 2026 at 05:27 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

TaskGroup

Un TaskGroup contiene subtareas creadas dinámicamente, que pueden ejecutarse de forma serial o concurrente. El grupo solo se considera terminado cuando todas las subtareas hayan finalizado.

withTaskGroup(of:returning:isolation:body:) permite crear el contexto para agregar subtareas usando addTask(priority:operation:).

Ejemplo básico: descarga de imágenes

func downloadPhoto(url: URL) async -> UIImage {
    // …
}

await withTaskGroup(of: UIImage.self) { taskGroup in
    let photoURLs = await listPhotoURLs(inGallery: "Vacaciones")
    for photoURL in photoURLs {
        taskGroup.addTask {
            await downloadPhoto(url: photoURL)
        }
    }
}

El TaskGroup actúa como un forEach que ejecuta cada subtarea de forma concurrente, almacenando los resultados.

Recopilando resultados

Como colección

Se puede definir el tipo de retorno como una colección (por ejemplo, [UIImage].self). Después de iniciar todas las subtareas, se usa la AsyncSequence del grupo para esperar los resultados y construir la colección resultante.

let images: [UIImage] = await withTaskGroup(
    of: UIImage.self,
    returning: [UIImage].self
) { taskGroup in
    let photoURLs = await listPhotoURLs(inGallery: "Vacaciones")
    for photoURL in photoURLs {
        taskGroup.addTask {
            await downloadPhoto(url: photoURL)
        }
    }

    var images = [UIImage]()
    for await result in taskGroup {
        images.append(result)
    }
    return images
}

Usando reduce

Como TaskGroup conforma AsyncSequence, también se puede emplear el operador reduce:

await withTaskGroup(of: UIImage.self, returning: [UIImage].self) { taskGroup in
    let photoURLs = try! await listPhotoURLs(inGallery: "Vacaciones")

    for photoURL in photoURLs {
        taskGroup.addTask {
            try! await downloadPhoto(url: photoURL)
        }
    }

    return await taskGroup.reduce(into: [UIImage]()) { partialResult, image in
        partialResult.append(image)
    }
}

ThrowingTaskGroup

Cuando alguna subtarea puede arrojar un error, se utiliza withThrowingTaskGroup(of:returning:isolation:body:).

try await withThrowingTaskGroup(of: UIImage.self, returning: [UIImage].self) { taskGroup in
    let photoURLs = try await listPhotoURLs(inGallery: "Vacaciones")

    for photoURL in photoURLs {
        taskGroup.addTask {
            try await downloadPhoto(url: photoURL)
        }
    }

    return try await taskGroup.reduce(into: [UIImage]()) { partialResult, image in
        partialResult.append(image)
    }
}

Comportamiento ante errores

  • Un ThrowingTaskGroup no falla automáticamente cuando una subtarea lanza una excepción; la subtarea se marca como fallida, pero el grupo continúa ejecutándose.
  • Para propagar el error y cancelar las tareas restantes, es necesario desempaquetar explícitamente cada subtarea con group.next().
try await withThrowingTaskGroup(of: Void.self) { group in
    group.addTask { throw SomeError() }
    // El grupo no falla aquí
}
try await withThrowingTaskGroup(of: Void.self) { group in
    group.addTask { throw SomeError() }
    try await group.next()   // Propaga el error y cancela tareas en curso
}

next()

group.next() entrega los resultados de las subtareas una a una, en orden aleatorio, y lanza el error correspondiente si la subtarea falla. Esto permite manejar cada error de forma individual.

while let result = try await taskGroup.next() {
    print("Got a new result:", result)
}

Cancelación de tareas

  • Se puede cancelar todo el conjunto de tareas con group.cancelAll().
  • Si se agrega una tarea a un grupo ya cancelado mediante addTask(priority:operation:), la tarea se cancela inmediatamente después de ser creada. Para evitar que la tarea siquiera comience, use addTaskUnlessCancelled(priority:operation:).
for photoURL in photoURLs {
    let didAddTask = taskGroup.addTaskUnlessCancelled {
        try await downloadPhoto(url: photoURL)
    }
    print("Added task: \(didAddTask)")
}
Back to Blog

Related posts

Read more »

SC #8: Cancelando un Task

Cancelación de Task en Swift y SwiftUI > Nota: En Swift, cancelar una Task no garantiza que la ejecución se detenga inmediatamente. Cada Task debe comprobar ma...

Approachable Swift Concurrency

Article URL: https://fuckingapproachableswiftconcurrency.com/en/ Comments URL: https://news.ycombinator.com/item?id=46432916 Points: 11 Comments: 0...

SC #10: Tarea desacoplada

Una tarea desacoplada Detached Task ejecuta una operación de forma asíncrona, fuera del contexto de concurrencia estructurado que la envuelve. No heredar este c...