Background: I am familiar with concurrency using Locks and Semaphores, and am learning Swift's new concurrency features.
Here is a simplified example of an asynchronous queue in Swift using DispatchSemaphore:
class AsynchronousQueue {
var data: [MyDataType] = []
var semaphore = DispatchSemaphore(value: 0)
func push(data: MyData) {
data.append(data)
semaphore.signal()
}
func pop() -> MyData {
semaphore.wait()
return data.popLast()
}
}
Note that this is a little bit oversimplified - I will also want to restrict the editing of the data array such that its not mutated by two threads at the same time, which can be accomplished with Locks, or with Actors. I also may want to send some sort of "cancel" signal, which also is a relatively small change.
The piece that I am not sure how to accomplish with Swift Concurrency is the role that the Semaphore plays here. I want pop() to return immediately if data is available, or to wait, potentially indefinitely, if data is unavailable.
I have encountered a lot of posts discouraging the use of Semaphores at all in modern Swift, code, but I have not seen an example of how to do this kind of waiting with Swift concurrency features (await/async/actor) that is not significantly more complicated than using a semaphore.
Note that DispatchSemaphore can't be used from within an async function, so it seems difficult to use the new await/async/actor features together with code based on DispatchSemaphore, which is why I ask.
Technically, what you have described is a stack (or a LIFO queue).
Here is my implementation.
It supports
push, blocking and non-blockingpopas well aspeekandcancel.isEmptyandstackDepthproperties are available.I have used a
CheckedContinuationto block thepopoperation if the stack is empty. The continuations are held in their own array to allow for multiple, blocked "poppers". Thecanceloperation cancels all outstandingpopOrWaitoperations. The cancelled calls willthrow.Blocked
popoperations complete in FIFO order.