Optimize the efficiency of your concurrent code with DiscardingTaskGroup
Previously, when we needed to handle concurrent processes without returning anything, we had to use TaskGroup
and specify a type like Void.self
. Let's see an example:
func execute() async {
await withTaskGroup(of: Void.self) { group in
for _ in list {
group.addTask {
await fetch()
}
}
...
}
}
In this code, we iterate overlist
and create a task to execute fetch()
for each member of the array.
But now, with the new addition at WWDC 2023, we have the WithDiscardingTaskGroup
API, which allows us to simplify the code:
func execute() async {
await withDiscardingTaskGroup { group in
for _ in list {
group.addTask {
await fetch()
}
}
...
}
}
Great, isn’t it? We no longer have to worry about specifying a return type like Void.self
.
Benefits of Using DiscardingTaskGroup
Using WithDiscardingTaskGroup
comes with other benefits that will help us write more efficient code:
- No retaining of results: the resources used by child tasks are immediately released after the task finishes. This helps reduce memory consumption when you have many tasks that don’t need to return anything.
- Automatically remove child tasks: so there’s no need to explicitly cancel the group or do cleanup. We no longer need to use
group.cancelAll()
, as we did when usingwithThrowingTaskGroup
. - Error handling: if any of the child tasks generates an error, all remaining tasks are automatically canceled. For cases where we have task groups that can potentially generate an error, we have
withThrowingDiscardingTaskGroup
.
Converting TaskGroup into DiscardingTaskGroup
Code like the following, where we need to:
- Execute
try await group.next()
to store the returned values. - Execute
group.cancelAll()
to release resources.
await withThrowingTaskGroup(of: Void.self) { group in
for _ in list {
group.addTask {
await fetch()
}
}
group.addTask {
try await Task.sleep(nanoseconds: 1_000)
}
try await group.next()
group.cancelAll()
}
Will become the following if we use WithDiscardingTaskGroup
:
await withThrowingDiscardingTaskGroup { group in
for _ in list {
group.addTask {
await fetch()
}
}
group.addTask {
try await Task.sleep(nanoseconds: 1_000)
}
}
With this small change, we can take advantage of all the benefits that withThrowingDiscardingTaskGroup
offers.
If you want to read the Spanish version of this article, you can find it here: https://asynclearn.com/blog/optimiza-la-eficiencia-de-tu-codigo-concurrente-con-discarding-task-group/