I am trying to use the TaskGroup feature in Swift 5.5. I would like to be able to manually cancel all subtasks as explained in the documentation.
I am using the code below and when I call cancelAll in withTaskGroup, everything works as expected and the uncompleted tasks are detected as isCancelled == true. However when I try to call it outside of withTaskGroup, I am unable to cancel taskgroup.
There is no problem if the cancel is done only for a single task (by using async).
Is there a problem with the cancel operation I perform outside of withTaskGroup? Or is it that taskgroup does not allow cancellation from outside.
@main
class Main{
static var taskGroup:TaskGroup<Void>? = nil
static var taskHandler:Task.Handle<Void,Never>?
static func main() async {
//Cancel TaskGroup Test
await withTaskGroup(of: Void.self){ group in
taskGroup = group
group.async {
print(await delayNumber())
}
/*
taskGroup?.cancelAll() // works. in withTaskGroup everything is woking fine.
*/
}
taskGroup?.cancelAll() // not work
//taskGroup = nil // not work too
//Cancel single Task Test
taskHandler = async {
print(await delayNumber())
}
// taskHandler?.cancel() // worked
//Task.sleep(10 * 1_000_000_000) // wait to finish
Thread.sleep(forTimeInterval: 10)
}
/*
Task.sleep now has a problem that can cause the code to crash. So some useless calculations are done to consume time
*/
static func delayNumber () async -> String{
for i in 0..<1_000_000_0{
if Task.isCancelled {
return "cancelled"
}
_ = Int.max - i
}
return String(Int.random(in: 0...1000))
}
}
As with other with APIs, you are *NOT* supposed to escape the closure parameter, i.e., group should not outlive the closure. This code would result in undefined behaviour.
Also, under normal circumstances, child tasks created from the task group will be awaited at the end of the closure, so there's nothing to cancel beyond that point.
Thank you for your reply.
It looks like I need to add another async outside of withTaskGroup to complete the control of taskGroup.
ps:The code is only for testing, I just want to understand the mechanism of cancellation
@main
class Main{
static var taskGroup:Task.Handle<Void,Never>? = nil // root task
static var taskHandler:Task.Handle<Void,Never>? = nil
static func main() async {
//Cancel TaskGroup Test
taskGroup = async { // root task
await withTaskGroup(of: Void.self){ group in
group.async {
print(await delayNumber())
}
/*
taskGroup?.cancelAll() // works. in withTaskGroup everything is woking fine.
*/
}
}
Thread.sleep(forTimeInterval: 3)
taskGroup?.cancel() // cancel root task will cancel all sub task
//Cancel single Task Test
taskHandler = async {
print(await delayNumber())
}
// taskHandler?.cancel() // worked
//Task.sleep(10 * 1_000_000_000) // wait to finish
Thread.sleep(forTimeInterval: 10)
}
/*
Task.sleep now has a problem that can cause the code to crash. So some useless calculations are done to consume time
*/
static func delayNumber () async -> String{
for i in 0..<1_000_000_0{
if Task.isCancelled {
return "cancelled"
}
_ = Int.max - i
}
return String(Int.random(in: 0...1000))
}
}
You might find the WWDC video below useful. The semantic of cancellation is just that "when a task is cancelled, so do all of its child tasks". So the important part is only to realize the relationship between each tasks, and figure out the implicit cancellation (if any) in your code.