the database could execute in the wrong order, printing
In GCD world we would execute the database writes on a serial queue, but with Swift Concurrency there seems to be no good way to mimic that behavior, since every suspension point entails a non deterministic execution order. I’m wondering how you guys are dealing with this. Is there a fundamentally different way I should be thinking about design patterns with Swift Concurrency? I have run into this problem in other contexts as well, for example, displaying photos in the order they were taken. It applies to anything where there’s some sort of input stream which goes into an async process that produces an output which should be ordered the same as the input.
If you need things to happen in a fixed order, then generally you should execute them in that order from a single task. Swift doesn't yet come out of the box with very well-developed mechanisms for this yet, but you might be able to use AsyncChannel and other tools from the swift-async-algorithms package to submit messages to a processing task to process in received order.
I've heard it mentioned, a few times, that the upcoming feature supporting custom actor executors will allow this. Although I've not seen any elaboration as to why.
Tangentially, I worry that this unintuitive behaviour of actors isn't well-advertised enough, in Swift. In every other language where I've encountered actors, order is deterministic in these sorts of scenarios because the actors have an actual "mailbox" (i.e. message queue)… behaving very much like a GCD serial queue. I'm actually quite curious why Swift's actors don't behave the same way, if not use that same kind of implementation, by default…?
Yeah, in their current form actors don't really "act" so much as they only provide a synchronization barrier for some data they own. One way to build a more traditional message-based model could be to have an actor that spawns a work task and message queue for itself, and have the public methods on the actor send messages and return immediately, letting the task work through the message queue in order.
Depends on the messaging mechanism you use. AsyncChannel should provide in-order delivery, and suspends senders when the buffer is full (or there is no buffer and the receiver isn't ready), but you could choose to use an unordered queue instead.