How to Use Task Group
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 havegroup
, 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
andasync let
, we can work with the results ofTaskGroup
without a defined order as they finish. For this, we use.next()
orfor 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/