Reputation: 17695
I am a bit confused about tasks being cancelled.
checkCancellation
function has 2 child tasks, one task runs computeA
and the other computeB
. They run concurrently, computeB
throws an error.computeA
to be cancelled because computeB
threw an error, but computeA
was never cancelled.SwiftUI
project (as Swift Playgrounds don't support async let
)macOS Big Sur 11.5.2 (20G95)
Xcode Version 13.0 beta 5 (13A5212g)
A - started
B - going to throw
A - going to return, Task.isCancelled = false
error: infinity
import Foundation
import UIKit
enum ComputationError: Error {
case infinity
}
fileprivate func computeA() async throws -> Int {
print("A - started")
await Task.sleep(2 * 100_000_000)
print("A - going to return, Task.isCancelled = \(Task.isCancelled)") //I expected Task.isCancelled to be true
return 25
}
fileprivate func computeB() async throws -> Int {
print("B - going to throw")
throw ComputationError.infinity
}
func checkCancellation() async throws {
async let a = computeA()
async let b = computeB()
let c = try await a + b
print("c = \(c)")
}
struct ContentView: View {
var body: some View {
Button("check cancellation") {
Task {
do {
try await checkCancellation()
print("normal exit")
} catch {
print("error: \(error)")
}
}
}
}
}
let c = try await b + a
A - started
B - going to throw
A - going to return, Task.isCancelled = true
error: infinity
I am still not sure I understand the reason for this behaviour in the original code
Upvotes: 1
Views: 2196
Reputation: 535222
I expected child task computeA to be cancelled because computeB threw an error, but computeA was never cancelled. Is my understanding wrong or am I missing something?
Yes and yes. Your understanding is wrong and you are missing something.
The remark in the video has to do with subtasks created in a task group. What actually happens is that when the error percolates from the child task up to the task group, the task group calls cancel
on all the other child tasks.
But you are testing using async let
. There is no task group. So there is no one "there" to cancel the other subtask. In fact, if you watch the video carefully, you'll find that it tells you correctly what to expect:
If the parent task is cancelled, the child tasks are cancelled automatically.
If an async let
task throws before a second async let
task is even initialized, then the second task is "born cancelled". It is still awaited (the video is very clear about this, and it is correct); if it doesn't respond to cancellation by aborting, it will continue to completion.
The point here is that the parent task must end in good order. It cannot end until its children have completed one way or another. (Again, the video is very clear about this.) If that means automatically waiting for the tasks to complete in spite of the throw, then that's what will happen.
Another interesting thing to note is that with async let
, where you say try await
later, if the subtask throws, you won't even hear about it (and nothing at all will happen in this regard) until you reach the point where the try await
takes place. In other words, the throw can only percolate up to the point where you actually say try
; nothing else would make sense.
Upvotes: 4