I did not consider the continuation solutions as my head was stuck on
signalling.
Right. The other alternative that makes sense for networking is to create an AsyncSequence
for incoming events. Honestly, I think that’s the right model but it makes flow control harder, which is why I’m personally staying away from it. I’ll let the Network framework engineers figure that out (-:
As side, would be interesting to know how the continuations work under
the hood as I have not come across that before
The bulk of the Swift concurrency runtime is in the Swift open source [1]. Be warned that looking at this may melt your brain (-:
Could you elaborate on the cancellation?
Cancellation is central to Swift concurrency. Things get cancelled all the time and it’s vital that you respond to cancellation requests promptly. Without that, the whole concept of structured concurrency breaks down.
If you’re unfamiliar with structured concurrency, I strongly recommend that you watch WWDC 2021 Session 10134 Explore structured concurrency in Swift.
When using continuations, a cancellation request must resume the continuation so that the async function blocked in withCheckedContinuation(…)
gets unblocked. That poses two questions:
The answer to the first is straightforward, but it has implications for the second. In this scenario [3], you learn about cancellation using withTaskCancellationHandler(…)
. That does the job, but you have to recognise an important limitation: The cancellation handler can be called in any context. So if it shares any state with, say, the underlying callback-based operation, that state must be protected. It also opens up a world of race conditions. For example, what happens if the cancellation occurred before you even started the underlying callback-based operation.
As to the second question, there are two basic approaches:
-
If the underlying callback-based operation supports cancellation, you can cancel it. The operation will naturally end, which then resumes the continuation.
-
If not, you can ‘orphan’ the underlying callback-based operation and resume the continuation from the cancellation handler.
Both of these present their own challenges. For the first, the underlying callback-based operation may put constraints on how you can cancel it. For example, it may require you to cancel from a particular thread or queue. It also may get grumpy if you cancel twice, a situation that’s hard to protect against.
For the second, you have to share state (the continuation) with the underlying callback-based operation, which means you need a lock. You also have to worry about the resource impacted of the orphaned operation.
In summary, I think that I know enough about cancellation to comprehend the problem space, but I don’t yet have any solutions that I’m prepared to stand behind. My experience, looking at code out there on the ’net, is that most folks apply the ostrich algorithm to this problem.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] My understanding is that some of the OS integration for Apple platforms exists within Apple’s private implementation of Dispatch [2]. However, the Swift open source is sufficient to get Swift concurrency running on non-Apple platforms, and so will probably cover the stuff that you’re interested in.
[2] Having said that, the source code for Apple’s private implementation of Dispatch is available in Darwin. Be warned that there are some cases where the Darwin code doesn’t 100% match the code being used by Apple. I haven’t dug into this stuff enough to know whether that’s relevant in this case.
[3] If your async function is CPU bound, poll for cancellation using Task.isCancelled
. That’s super easy to do, but not relevant here.