I found that Task cancellation state is not propagated when any task from tasks group has failed with error.
In my example the 2 long running async operations starts simultaneously. The 1st one lasts 3sec and fails. The 2nd one lasts 6sec and completes with success. Both tasks check Task.isCancelled
state before completion. Actually I see that 6s task is not receiving Task.isCancelled
state.
My expectation was as soon as 1st task fails then tasks group sends cancel
to all remaining tasks in the group. So 6sec task should pass checking for Task.isCancelled
Below the source code of long running async operation, it emulates long running executing via sleep()
. After sleep()
it checks Task.isCancelled
state:
func runLongOperation(key: String, sleepSec: UInt32, shouldFailWithError: Bool) async throws -> String {
try await withUnsafeThrowingContinuation { continuation in
let queue = DispatchQueue(label: "loq")
queue.async {
print(" \(key) started...")
sleep(sleepSec)
if Task.isCancelled {
print(" ...\(key) Task is cancelled")
}
if shouldFailWithError {
print(" ...\(key) finished with failure")
continuation.resume(throwing: MyError.generic)
}
else {
print(" ...\(key) finished with success")
continuation.resume(returning: key)
}
}
}
}
This async function executes 2 long running operations simultaneously in tasks group:
func runTasksGroup() async throws -> [String] {
return try await withThrowingTaskGroup(of: String.self, body: { group in
group.addTask {
return try await self.runLongOperation(key: "#1[3s]", sleepSec: 3, shouldFailWithError: true)
}
group.addTask {
return try await self.runLongOperation(key: "#2[6s]", sleepSec: 6, shouldFailWithError: false)
}
var strs = [String]()
for try await str in group {
strs += [str]
}
return strs
})
}
And runTasksGroup
function starts from sync viewDidLoad()
function
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
let strs = try await runTasksGroup()
print("all done with success \(strs.joined(separator: " "))")
}
catch {
print("some tasks failed")
}
}
}
The output
#1[3s] started...
#2[6s] started...
...#1[3s] finished with failure
...#2[6s] finished with success
some tasks failed
The task which lasts 6sec doesn't receive Task.isCancelled
state, although 3sec task has failed earlier.
Could anybody explain why the 6s task is not cancelled?