În timp ce directiva async let este excelentă pentru a rula un număr fix și cunoscut în avans de sarcini paralele, aplicațiile reale necesită frecvent procesarea unei colecții dinamice de elemente (descărcarea a zeci de imagini, interogarea a numeroase microservicii paralele). Pentru aceste scenarii, Swift oferă Task Groups prin funcțiile withTaskGroup și withThrowingTaskGroup.
Grupul de sarcini respectă principiile fundamentale ale concurenței structurate (Structured Concurrency): niciun task copil nu poate supraviețui domeniului funcției părinte, iar dacă un task aruncă o eroare, celelalte sarcini active sunt marcate automat pentru anulare (cancellation propagation).
import Foundation
struct ImagineRezultat: Sendable {
let id: Int
let dimensiuneOcteti: Int
}
func descarcaToateImaginile(idUri: [Int]) async throws -> [ImagineRezultat] {
// Inițializăm un grup structurat ce returnează instanțe de tip ImagineRezultat:
try await withThrowingTaskGroup(of: ImagineRezultat.self) { group in
for id in idUri {
// Adăugăm dinamic câte o sarcină paralelă în grup:
group.addTask {
// Verificăm dacă sarcina nu a fost deja anulată cooperativ:
try Task.checkCancellation()
let url = URL(string: "https://images.dexblog.ro/img_\(id).jpg")!
let (data, _) = try await URLSession.shared.data(from: url)
return ImagineRezultat(id: id, dimensiuneOcteti: data.count)
}
}
// Colectăm rezultatele asincron pe măsură ce sarcinile se termină:
var rezultate: [ImagineRezultat] = []
rezultate.reserveCapacity(idUri.count)
for try await imagine in group {
rezultate.append(imagine)
}
return rezultate
}
}
Pattern de Throttling (Limitarea Concomitenței)
Pentru a evita epuizarea socket-urilor de rețea sau a memoriei când procesați mii de elemente, puteți limita numărul de sarcini active la o fereastră fixă (ex: maximum 4 în paralel):
try await withThrowingTaskGroup(of: Void.self) { group in
let maxConcurent = 4
var indexCurent = 0
// Lansăm primele N sarcini:
while indexCurent < idUri.count && indexCurent < maxConcurent {
let item = idUri[indexCurent]
group.addTask { try await proceseazaItem(item) }
indexCurent += 1
}
// Pe măsură ce un task se finalizează, lansăm următorul item din coadă:
for try await _ in group {
if indexCurent < idUri.count {
let item = idUri[indexCurent]
group.addTask { try await proceseazaItem(item) }
indexCurent += 1
}
}
}