How to Use Task Group

AsyncLearn
3 min readNov 13, 2023

--

For cases where the number of concurrent processes is determined at runtime, we can use TaskGroup. These contain groups of asynchronous tasks that are created dynamically.

To create a TaskGroup, we use withThrowingTaskGroup(of:returning:body:):

await withThrowingTaskGroup(of: String.self, 
returning: [String]) { group in
...
}

With this code, we create a TaskGroup, passing the following parameters:

  • of indicates what type of data the child tasks will handle.
  • returning indicates what type of data will be returned once all tasks in the group have executed.
  • operation, in this case, we use a trailing closure, so it's not necessary to indicate the name of the last parameter. Inside this closure, we have group, which is the object we will use to add child tasks.

Adding Tasks

To add tasks to our TaskGroup, we use the following code:

await withThrowingTaskGroup(of: Void.self) { group in
group.addTask(priority: .high) {
try await fetch()
}
}

Using addTask(priority:operation:), we can optionally specify the priority. If not specified, it inherits the priority from the context. We also pass a code block where the task we want to add is located.

There is an alternative to this method, addTaskUnlessCancelled(priority:operation:). The only difference is that with this one, the task will only be added if the TaskGroup has not been canceled previously.

How to Cancel Tasks within a TaskGroup

TaskGroup has a method to cancel tasks that haven't been executed: cancelAll().

await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
await fetch()
}

group.addTask {
await update()
}

if condition {
group.cancelAll()
}
}

Note that in this example, we pass Void.self to the of parameter, so we won't return anything.

In this case, we evaluate condition, and if it's true, we execute cancelAll(). However, if any of the previously added tasks has already finished, we will see the result, as only pending tasks are canceled.

Also, if one of the child tasks produces an error, the remaining tasks will be canceled.

Another way cancellation can occur is if the parent task of the TaskGroup is canceled.

Dealing with Responses in a TaskGroup

try await withThrowingTaskGroup(of: String.self) { group in
group.addTask {
await fetch()
}

group.addTask {
await pull()
}

if condition {
group.cancelAll()
}

for try await result in group {
print(result)
}
}

In this code, we create two child tasks that return a String using the asynchronous functions fetch() and pull(). However, we don't want to wait for both of them to finish before printing the returned values. In this case, we can use for await, which iterates over the results of group. This is possible because group conforms to AsyncSequence and implements next().

Things to Consider

  • If you encounter an exception in one of these tasks while working on them, all remaining tasks in the group will be canceled and stopped automatically.
  • All tasks within a group must return the same data type.
  • Unlike Task and async let, we can work with the results of TaskGroup without a defined order as they finish. For this, we use .next() or for await.

If you want to read the Spanish version of this article, you can find it here: https://asynclearn.com/blog/como-utilizar-task-group/

--

--

AsyncLearn

Stay up-to-date in the world of mobile applications with our specialised blog.