There is one remaining "corner" of the non-concurrent async/await universe that I'm unclear about.
Currently, we can get the effect of "interleaving" "partial tasks" (using those terms in an analogous way, not literally as defined in this proposal) on a single thread, using DispatchQueue.async
with a serial dispatch queue.
This pattern is useful for simple cases where variable mutation must be atomic and we want synchronous regions of code, but larger data races are not an issue. In practical terms, we often write code for the main thread/queue in order to avoid having to write explicit synchronization or data protection infrastructure. (Less often we do it for a "background" thread/serial queue, but the reasoning is the same.)
AFAICT, this possibility does not exist within the async
/await
portion of this proposal. In other words, given two Task
s, there is going to be a way to run them concurrently (possibly in parallel on multiple threads) — runDetached
— but there is no way to run them non-concurrently (possibly interleaved) on the same thread (and not in parallel on multiple threads). Maybe I mean "on the same executor" here, I'm not sure.
Again AFAICT, the possibility is not proposed here because the pattern is intended to be subsumed into the global actor pattern. That's good, because the global actor pattern would make such things actually safe, rather than hopefully safe (i.e. safe if we don't make silly mistakes).
It's also somewhat bad, though, because:
-
It requires coming to grips with actors before allowing the use of async
/await
to improve some of the completion-handler-based patterns we use today. That's going to be a bit off-putting to developers new to Swift or asynchronicity, I think.
-
It requires all methods being used in the pattern to be explicitly marked as UIActor
or some such global name. That seems like a lot of boilerplate to get many methods to participate in a simple pattern.
-
It requires all methods marked with a global actor to be restricted to that actor, and prevents them being used by "the same actor as code in another Task
, whatever it might be".
-
Protecting ordinary state directly with a non-global actor requires even more hands-on knowledge of actors than #1, since (IIUC) it would involve moving the state into that non-global actor class.
Is it possible that we could have (say) runInterleaved
that would run the specified task interleaved with the current task, but non-concurrently? Or is there something so egregiously wrong with our current serial queue pattern that we must disable it going forward?
I had hoped that the forthcoming @asyncHandler
proposal would include this functionality, but comments upthread seem to say that the underlying API for @asyncHandler
would be runDetached
(concurrent) rather than a hypothetical runInterleaved
(non-concurrent).