i have 3 tasks arranged roughly so:
let task:Task<Void, Never> =
{
var sleep:Task<Void, Never> = .init
{
try? await Task.sleep(for: .milliseconds(heartbeat))
}
// sends the sleep task to someone who might cancel it
send(sleep: sleep)
await sleep.value
}
task.cancel()
// will this resume immediately?
await task.value
-
main task: awaits
task, can cancel task
-
task: started from the main task, creates sleep tasks that escape to someone who may cancel them early, and awaits on those sleep tasks.
-
sleep: started from the task task, sleeps until the time runs out, or someone cancels it.
now, my question is: what happens to the sleep tasks if the main task cancels the task task?
is the
await task.value
line guaranteed to resume immediately?
so, testing this out on a nightly toolchain, it looks like the answer is no:
@main
enum Main
{
public static
func main() async
{
let task:Task<Void, Never> = .init
{
let sleep:Task<Void, Never> = .init
{
try? await Task.sleep(for: .milliseconds(2_000))
}
await sleep.value
}
task.cancel()
// will this resume immediately?
await task.value
}
}
$ time ./cancellation
real 0m2.006s
user 0m0.006s
sys 0m0.000s
we have to do
@main
enum Main
{
public static
func main() async
{
let task:Task<Void, Never> = .init
{
let sleep:Task<Void, Never> = .init
{
try? await Task.sleep(for: .milliseconds(2_000))
}
await withTaskCancellationHandler
{
@Sendable () -> () in await sleep.value
}
onCancel:
{
sleep.cancel()
}
}
task.cancel()
await task.value
}
}
$ time ./cancellation
real 0m0.005s
user 0m0.005s
sys 0m0.000s
ktoso
(Konrad 'ktoso' Malawski 🐟🏴☠️)
3
You're using un-structured tasks here, so there's no cancellation relation between any of these tasks.
Only child tasks, i.e. "structured concurrency", have the property that "inner" ("child") tasks are cancelled when their parents are. Only ways to create child tasks are: TaskGroup and async let.
At least in this toy example, the sleep task could be an async let or a group child task and you'd get cancellation propagation as expected.
// Relatedly, I'll see if/how/when we can update the swift book to provide more details about these things
6 Likes
mredig
(Michael Redig)
4
Wait, so
let parent = Task {
print("parent start")
let child = Task {
print("child start")
try await Task.sleep(nanoseconds: 3_000_000_000)
print("child finish")
}
try await child.value
print("parent finish")
}
is not only not structured concurrency, my variable names are lying to me?!
for the longest amount of time i thought “structured concurrency” meant Task, and “unstructured concurrency” referred to things like EventLoopFuture...
1 Like
ktoso
(Konrad 'ktoso' Malawski 🐟🏴☠️)
6
That’s right.
Check out the structured concurrency talks from wwdc.
Also, there’s a new talk coming up also covering this: https://twitter.com/ktosopl/status/1601151750526095360
I agree we should have better and more explicit docs in the swift book about this btw. It’s a … process, to get things fixed there but we should be able to do so soon since it’s become open source and easier to submit changes to 
3 Likes
ktoso
(Konrad 'ktoso' Malawski 🐟🏴☠️)
7
yeah, i figured that out around late 2021, my point is the swift 5.5+ concurrency features were pitched as “adding structured concurrency” to the language, so for a long time i thought all of the new features (including Task) counted as “structured concurrency”.
i think a lot of the early docs also fixated a lot on the difference between Task.init and Task.detached, which made it seem like “structuredness” had something to do with “detachedness”.
2 Likes
ktoso
(Konrad 'ktoso' Malawski 🐟🏴☠️)
9
We have to improve the docs around these definitely. Thanks for the reminder
1 Like
mredig
(Michael Redig)
10
Exactly this - I thought that .detached was what broke it out the structure, not to mention that there was some _inherentContext or something similar in the Task initialization, which I took to mean that it was inheriting structure from the parent.
1 Like
mredig
(Michael Redig)
11
I'll also add that I've watched that video more than once. Perhaps it's my fault for not letting that point sink in, but maybe it could have been clearer in the video? I'll have to watch it again with new context to provide better feedback.