|< How to handle different result types in a task group||How to make async command-line tools and scripts >|
Updated for Xcode 14.2
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.
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
cancelAll() respectively. There is no equivalent for
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:
async letthe most; it works best when there is a fixed amount of work to do.
Taskfor some places where
async letdoesn’t work, such as passing an incomplete value to a function.
I find that order is pretty accurate in practice, for a number of reasons:
Taskis similar enough to
async letthat it’s easy to move across to
Taskwithout 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% 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.
Link copied to your pasteboard.