Asynctaskgroup
Async TaskGroup
Important for kicking off new tasks and not waiting for async tasks to complete and then move to next line of code like a imperative programming paradigm.
You can utilize async let
for kicking off request
For TaskGroup with throwing tasks.
withThrowingTaskGroup(of: Type.self)
Add these async tasks to the execution scheduler group: TaskGroup
.
group.addTask {
try await doSomeAsyncTask()
}
Usually good idea to iterate over the array of data to pull concurrent tasks which are not dependent on each other and are mutually exclusive.
Code
Full code for easier consumption of a task.
func fetchImagesTaskGroup() async {
if let images = try? await fetchImagesAsyncTaskGroup() {
self.imagesAsyncGroup.append(contentsOf: images)
}
}
func fetchImagesAsyncTaskGroup() async throws -> [UIImage] {
return try await withThrowingTaskGroup(of: UIImage?.self) { [weak self] group in
guard let self = self else { throw error }
var images: [UIImage] = []
images.reserveCapacity(imagesArr.count)
for imageURL in imagesArr {
group.addTask {
try await self.fetchSingleImage(url: imageURL)
}
}
for try await image in group {
if let image = image { images.append(image) }
}
return images
}
}
func fetchSingleImage(url: String) async throws -> UIImage {
guard let url = URL(string: url) else { throw URLError(.badURL) }
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else { throw error }
return image
}
Failing task in multiple tasks
You can also utilize optionals for your Type
when we want to still proceed with running asynchronous tasks results.
Like Type?.self
to denote that out of n
network request one can fail and you don't want that failure to interrupt or infinitely wait out for getting results or fire more chain events dependent on a result. Swift strictly typed language gives us a way to properly harness that with defining optionality. Adhering internally to ExpressibleByNil
protocol.
withThrowingTaskGroup(of: CustomModel?.self)
Order guarantee
Guaranteeing the order of async execution is important.
Easier solution is to use a dictionary to do unique Key mapping and then depending on what you want at the end of the async function you could return a dictionary or ordered array.
It would of course add one more O(n)
complexity towards the creation of sorted array again from the dictionary.
Code snippet from SO post
let stooges = ["moe", "larry", "curly"]
let images = await downloadImages(names: stooges)
imageView1.image = images["moe"]
imageView2.image = images["larry"]
imageView3.image = images["curly"]
func downloadImages(names: [String]) async -> [UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}
let dictionary = await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
return names.compactMap { dictionary[$0] }
}
}
Or you can also use tuples while making the .addTask { }
call
let countries = await Server.shared.getCountries()
print(countries)
var capitals = [(country:String, capital:String)](/404)
await withTaskGroup(of: (String,String).self) { group in
for country in countries {
group.addTask {
let capital = await Server
.shared
.getCapital(of:country)
return (country,capital)
}
}
for await pair in group {
capitals.append(pair)
}
}
print(capitals)
Good article about maintaining order in async concurrent loops.
Async Loops
asynchronous-looping-with-async-await
how-to-loop-over-an-asyncsequence-using-for-await