Optimize the efficiency of your concurrent code with DiscardingTaskGroup

AsyncLearn
2 min readOct 30, 2023

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 overlistand 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 using withThrowingTaskGroup.
  • 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/

--

--

AsyncLearn

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