Logo for tanaschita.com

How to call async/await functions concurrently in Swift

Learn how to call async/await functions in parallel with tasks.

30 Jun 2021 · 4 min read

As we have learned in this guide on async/await in Swift, calling an asynchronous function with await runs only one piece of code at a time. The caller waits for it to finish before running the next line of code.

Sometimes, we want to be able to start multiple async functions in parallel because they don't depend on each other. This is where tasks come in.

Calling asynchronous functions concurrently with tasks

Tasks are a new feature in Swift version 5.5 that work hand in hand with the new async/await functionality. Each task runs concurrently, so tasks allow us to call asynchronous functions in parallel.

Instead of directly calling await on a async function, we can use the async-let binding to create a task which provides a new async context for executing code concurrently.

Sequential binding:

let profileImage = await URLSession.shared.data(...)
let headerImage = await URLSession.shared.data(...)

Concurrent binding:

async let profileImageTask = URLSession.shared.data(...)
async let headerImageTask = URLSession.shared.data(...)
let profileImage = await profileImageTask
let headerImage = await headerImageTask

With the concurrent binding approach, we created two child tasks that start independently without waiting for the previous one to complete.

Group tasks

Additionally to async-let tasks, Swift provides group tasks.

Group tasks are designed to provide a dynamic amount of concurrency, i.e. they are perfect for situations, where we don't know the amount of concurrent tasks from the beginning, for example when fetching thumbnails from an array of urls.

func downloadImages(imageURLs: [URL]) async throws -> [UIImage] {
var results: [UIImage] = []
try await withThrowingTaskGroup(of: (Data, URLResponse).self, body: { taskGroup in
for imageURL in imageURLs {
taskGroup.async { try await URLSession.shared.data(from: imageURL) }
}
while let data = try await taskGroup.next(), let image = UIImage(data: data.0) {
results.append(image)
}
})
return results
}

Each task in a group has the same parent task, and each task can have child tasks. Because tasks are arranged in a hierarchy, this approach is called structured concurrency.

Related tags

Written by