Clarification needed on UnsafeContinuation documentation

I'm not totally sure how to respond to this. You're arguing that there's a lot of confusion about Swift concurrency, and that's very convincing, because your post also asserts a lot of stuff that's wrong. I think you've misunderstood some of the basic terms in use in Swift concurrency, so let me try to clear things up.

It sounds like you're using "task" as if it's basically a scheduling unit — the amount of code that would be indivisibly scheduled by a single call to, say, dispatch_async. In Swift concurrency, we use the term "job" or "partial task" for that. A "task" is an asynchronous thread, which is ultimately executed as a sequence of scheduling units; those units never execute concurrently with one another, and are in fact totally sequential, and their execution is formally well-ordered with respect to concurrency so that the events in one unit must all happen-before the events in the next.

Continuations are not an exception to this. withUnsafeContinuation does not return until both something has called resume on the continuation and the function passed to withUnsafeContinuation has returned, and that is also formally well-ordered with respect to concurrency. So it is absolutely not the case there are somehow two tasks involved with continuations or that the "second task" can start running before the closure has returned.

Now, there is a bug in Xcode 14 when compiling for macOS because it ships with an old macOS SDK. That bug doesn't actually break any of the ordering properties above. It does, however, break Swift's data isolation guarantees because it causes withUnsafeContinuation, when called from an actor-isolated context, to send a non-Sendable function to a non-isolated executor and then call it, which is completely against the rules. And in fact, if you turn strict sendability checking on when compiling against that SDK, you will get a diagnostic about calling withUnsafeContinuation because it thinks that you're violating the rules (because withUnsafeContinuation doesn't properly inherit the execution context of its caller).

But that has nothing to do with the basic correctness of the order of execution on a task, and its only relation to the scheduling of partial tasks is that it incorrectly creates suspension points at the call to and return from withUnsafeContinuation, forcing more partial tasks to be scheduled. (There is not otherwise necessarily a suspension point on the return from withUnsafeContinuation — if the function passed in manages to call resume on the continuation before it returns, then withUnsafeContinuation will return without the task ever having been suspended.)

16 Likes