WWDC23 SALE: Save 50% on all my Swift books and bundles! >>

What’s the difference between async let, tasks, and task groups?

Paul Hudson    @twostraws   

Updated for Xcode 14.2

Swift’s async let, Task, and task groups all solve a similar problem: they allow us to create concurrency in our code so the system is able to run them efficiently. Beyond that, the way they work is quite different, and which you’ll choose depends on your exact scenario.

To help you understand how they differ, and provide some guidance on where each one is a good idea, I want to walk through the key behaviors of each of them.

First, async let and Task are designed to create specific, individual pieces of work, whereas task groups are designed to run multiple pieces of work at the same time and gather the results. As a result, async let and Task have no way to express a dynamic amount of work that should run in parallel.

For example, if you had an array of URLs and wanted to fetch them all in parallel, convert them into arrays of weather readings, then average them to a single Double, task groups would be a great choice because you won’t know ahead of time how many URLs are in your array. Trying to write this using async let or Task just wouldn’t work, because you’d have to hard-code the exact number of async let lines rather than just loop over an array.

Second, task groups automatically let us process results from child tasks in the order they complete, rather than in an order we specify. For example, if we wanted to fetch five pieces of data, task groups allow us to use group.next() to read whichever of the five comes back first, whereas using async let and Task would require us to await values in a specific, fixed order.

That alone is a helpful feature of task groups, but in some situations it goes from helpful to crucial. For example, if you have three possible servers for some data and want to use whichever one responds fastest, task groups are perfect – you can use addTask() once for each server, then call next() only once to read whichever one responded fastest.

Third, although all three forms of concurrency will automatically be marked as cancelled if their parent task is cancelled, only Task and task group can be cancelled directly, using cancel() and cancelAll() respectively. There is no equivalent for async let.

Fourth, because async let doesn’t give us a handle to the underlying task it creates for us, it’s not possible to pass that task elsewhere – we can’t start an async let task in one function then pass that task to a different function. On the other hand, if you create a task that returns a string and never throws an error, you can pass that Task<String, Never> object around as needed.

And finally, although task groups can work with heterogeneous results – i.e., child tasks that return different types of data – it takes the extra work of making an enum to wrap the data. async let and Task do not suffer from this problem because they always return a single result type, so each result can be different.

By sheer volume of advantages you might think that async let is clearly much less useful than both Task and task groups, but not all those points carry equal weight in real-world code. In practice, I would suggest you’re likely to:

  • Use async let the most; it works best when there is a fixed amount of work to do.
  • Use Task for some places where async let doesn’t work, such as passing an incomplete value to a function.
  • Use task groups least commonly, or at least use them directly least commonly – you might build other things on top of them.

I find that order is pretty accurate in practice, for a number of reasons:

  1. I normally want results from all the work I start, so being able to skip some or get results in completion order is less important.
  2. It’s surprisingly common to want to work with different data types, which is clumsy with task groups.
  3. If I need to be able to cancel tasks, Task is similar enough to async let that it’s easy to move across to Task without going all the way to a task group.

So, again I would recommend you start with async let, move to Task if needed, then go to task groups only if there’s something specific they offer that you need.

Save 50% in my WWDC23 sale.

SAVE 50% To celebrate WWDC23, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Similar solutions…

Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.