I am new to Swift Concurrency and trying to understand Task -> ChildTask relationship.
I created here two Tasks
- Parent Task :
- (1)Calls the test() method in Actor from MainThread.
- (3.2)The Actor calls the test() method in Background Thread…
- (2)then returns the control to the parent task on the MainThread
- Child Task:
- (3)Runs in MainThread
class MyViewController: ViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("1 (Thread.current.isMainThread)")
Task {
async let _ = ViewModel().test() // Throws 3.3 CancellationError()
// await ViewModel().test() // Does not cancel task and works fine
print("2 (Thread.current.isMainThread)")
Task {
do {
Thread.sleep(forTimeInterval: 0.5)
print("4.1 (Thread.current.isMainThread)")
} catch {
print("4.2 (Thread.current.isMainThread)")
print(error)
}
}
}
}
actor ViewModel {
func test() async {
print("3.1 (Thread.current.isMainThread)")
do {
print("3.2 (Thread.current.isMainThread)")
let images = try await downloadImage()
} catch {
print("3.3 (error)")
}
}
func downloadImage() async throws -> UIImage {
try Task.checkCancellation()
await Thread.sleep(forTimeInterval: 1) // 1seconds
return UIImage()
}
}
}
async let _ = ViewModel().test() // Throws 3.3 CancellationError()
// await ViewModel().test()
1 true
2 true
3.1 false
3.2 false
3.3 CancellationError()
4.1 true
// async let _ = ViewModel().test()
await ViewModel().test() // Does not cancel task and works
1 true
3.1 false
3.2 false
2 true
4.1 true
My Question here is, why is the Task is cancelled when I dont wait for the async_let test() method
2
Answers
As @Sweeper told, The parent task won't wait for the child task to complete, they only share the Task's Context (whether it is to be cancelled or the thread it was started from)
I added some
defer {logs}
to the task, and found that the parent task is deallocatedprints 5.x
before the cancellation. and so the async_let_task is also cancelled...You should not use
Thread.current
orThread.sleep
inasync
contexts. This will be an error in Swift 6. Let’s remove those and just consider this code:You seem to think that the task that waits for 0.5 seconds is a "child" of the task that runs
ViewModel().test()
. This is not true. By usingTask { ... }
, you start a top level task that is not a child of anything. And you don’tawait
this task, so all the outerTask
does, is start the task ofViewModel().test()
, start another top level task (doesn’t wait for it), and ends.To actually wait for the top-level task, you can do:
But if all you want is to wait for some time, you don’t need a top-level task at all.Just directly write:
Now the task of "
Task.sleep
" is a child task of the single top level task you created. I’ll assume you made the above change from now on.Unlike
await
which actually waits,async let
runs in parallel with the code around it. See this section in the Swift guide for an example. No one is waiting for it to complete if you don’tawait
the variable created bylet
(which you didn’t even a give a name to) at some point.So no one waits for
ViewModel().test()
to complete. You only wait for 0.5 seconds after launchingViewModel().test()
in parallel, and that’s not enough time forViewModel().test()
to finish. After 0.5 seconds, the top level task ends, the task runningViewModel().test()
gets cancelled because it is a child task of the top level task. That explains theCancellationError
.