Why `AsyncStream` breaks structured concurrency?

That's correct. Cancelling a parent task will automatically cancel child tasks.

if you have a task which runs in the AsyncStream continuation block [cancellation does not propagate]

Your code in the AsyncStream closure does not spawn a child task. The Task {} API creates an unstructured Task. Unstructured tasks do not automatically inherit cancellation from whomever created them.

While this might not be a solution for your problem I'd like to point out the proper terminology and behavior because this seems to be a constant source of confusion regarding Swift concurrency.

Child Tasks can only be spawned from an async context using async let or task groups. Their common behavior is that child tasks have to terminate before the spawning function returns. Cancellation is inherited from the parent task, as well as task priority and task local values.

Unstructured Tasks on the other hand can be spawned from everywhere, using Task {} or Task.detached {}. The spawning function can return before the unstructured Task finishes (that's why they are called unstructured). Cancellation is not automatically propagated. Task.detached {} also resets priority and actor context.

While the Xcode documentation for Task is somewhat confusing @ole pointed out that this WWDC talk gives a good overview:

20 Likes