Async Await in Swift — Part3

AmitAswal
4 min readJul 12, 2024

--

In Part1 we have discussed synchronous code , asynchronous code, what was the need of async / await when we were already having async method calling available, with example.

In Part2 we have discussed how Pyramid of Doom can be avoided using Async / Await.

Now lets see :

  1. How can we call multiple Async Methods in Parallel at once
  2. How to use Tasks Groups using Async Await.

Calling Asynchronous Functions in Parallel

Using async let in Swift to call asynchronous functions in parallel provides several significant advantages, particularly in terms of efficiency, responsiveness, and performance. Here's a more detailed explanation of how it works and its benefits:

Running Asynchronous Functions in Parallel

When using await, the code executes one asynchronous task at a time, waiting for each task to complete before moving on. However, to run multiple asynchronous functions simultaneously, you can use async let. This allows multiple asynchronous operations to start concurrently without waiting for each other.

Example

async let image1 = downloadImage(from: url1)
async let image2 = downloadImage(from: url2)
async let image3 = downloadImage(from: url3)
async let image4 = downloadImage(from: url4)

let images = await [image1, image2, image3, image4]

In this example, the calls to downloadImage are initiated in parallel, and the code proceeds to the next line immediately. It waits only when the results are needed, using await to pause execution until all four downloads complete.

Advantages

  1. Improved Efficiency: By starting multiple asynchronous operations concurrently, the overall task completes faster than running them sequentially. This is because the operations run in parallel rather than waiting for each one to finish before starting the next.
  2. Better Resource Utilization: Parallel execution makes better use of system resources, such as CPU and network bandwidth. While one operation is waiting for a network response, another can utilize the CPU to process data, leading to more efficient resource usage.
  3. Reduced Latency: In scenarios like downloading multiple images, the total wait time is reduced. Instead of waiting for each download to complete one after the other, all downloads happen simultaneously, significantly reducing the overall waiting time.
  4. Responsive Applications: For user-facing applications, running tasks in parallel keeps the app more responsive. Users don’t experience delays caused by sequential task execution, improving the user experience.
  5. Cleaner Code: The use of async let results in cleaner and more readable code compared to managing parallelism manually with completion handlers or dispatch groups. The intent to run tasks in parallel is explicitly clear, making the code easier to understand and maintain.

Task Groups

In Swift, you can use Task Groups to execute multiple asynchronous tasks in parallel, efficiently handling concurrent operations. This feature, introduced in swift, allows you to manage and control the lifecycle of concurrent tasks within a specific scope. Let’s see how you can utilize Task Groups to download multiple images concurrently from a list of URLs.

Example with Task Groups

Here’s how you can use Task Groups to download images from an array of URLs concurrently:

func download() async -> [UIImage] {
let urls = [
URL(string: "https://images......")!,
URL(string: "https://images......")!,
URL(string: "https://images......")!,
URL(string: "https://images......")!,
URL(string: "https://images......")!,
URL(string: "https://images......")!,
URL(string: "https://images......")!,
URL(string: "https://images......")!,
URL(string: "https://images......")!,
URL(string: "https://images......")!,
]

return await withTaskGroup(of: UIImage?.self) { group in
for url in urls {
group.addTask {
return await self.downloadImage(from: url)
}
}
defer {
group.cancelAll()
}
var images: [UIImage] = []
for await image in group {
if let image = image {
images.append(image)
}
}
return images
}
}

private func downloadImage(from url: URL) async -> UIImage? {
do {
let (data, _) = try await URLSession.shared.data(from: url)
return UIImage(data: data)
} catch {
print("Failed to download image from \(url): \(error)")
return nil
}
}

Explanation

  1. Task Group Creation:
  • withTaskGroup(of: UIImage?.self) { group in ... }: This API creates a task group where each task will return an optional UIImage.

2. Adding Tasks to the Group:

  • group.addTask { ... }: Within the task group, you add a new task for each URL to download the image concurrently. Each task calls downloadImage(from:).

3. Handling Task Results:

  • The defer block ensures that if the scope exits early due to an error, all tasks are cancelled.
  • As tasks complete, their results are collected. Each task downloads an image, and if successful, it is appended to the images array.

4. Function Execution:

  • The downloadImage(from:) function performs the actual image download using URLSession and handles any errors by returning nil if the download fails.

Advantages

  • Concurrency: Tasks run in parallel, utilizing system resources efficiently to download multiple images simultaneously.
  • Error Handling: If any task throws an error, it will not affect the execution of other tasks.
  • Readability and Maintainability: The code is clean, easy to understand, and follows a linear execution model despite being concurrent.

This approach provides a structured way to handle multiple asynchronous operations concurrently, improving the efficiency and responsiveness of your app.

If you like this post, please share and give claps so others can find it 👏👏

--

--