+0.5, I empathize with the folks saying that this feature will be somewhat hobbled by lack of proper backpressure support.
The way I see it, there are three main cases where a simplified implementation of AsyncSequence
would be useful:
- To bridge API you don't own which calls back in a context you cannot synchronize with.
buyVegetables
is an example of this from the proposal. If it is backed by aDispatchQueue
, we cannotasync
onto that queue, only provide callbacks. - To bridge API that you don't own, that calls back in a context that you can synchronize with.
DispatchSource.makeSignalSource
is an example of this, because you can provide your ownDispatchQueue
andasync
onto that. - Bridging API you own, where you might want to be smart about how you deal with back pressure.
This proposal addresses item 1, and partially addresses item 2 with the caveat that it may end up using a suboptimal locking primitive to perform synchronization. It does nothing for case 3.
I recently wrote an implementation of AsyncSequence
for NIO's ChannelInboundHandler
(thread), and after thinking about it some more I believe this proposal can be generalized in a way that addresses all three cases. The basic idea would be to provide a few more configuration points. Namely, allow users to specify the synchronization mechanism to use and allow direct access to the buffer.
By default, we can provide the same locking behavior proposed here, but we can also allow users to implement a function to use the locking mechanism that makes sense for their use case. For DispatchSignalSource
this would be DispatchQueue.async
, while for NIO
this would be EventLoop.execute
.
The infrastructure can then use the locking mechanism to store continuation and react to new data becoming available, much like I do in my earlier example. By default this can have an infinite buffer, but more advanced use cases can inspect the buffer state like how many items are buffered, and whether the stream is currently being awaited (and a callback for when the stream moves from unawaited -> awaited).
Such an approach could also enable progressive improvement of a bridged API, so folks can improve a naive implementation by adding back pressure handling without ditching the AsyncStream
implementation (and be sure they address some of the more nuanced issues of a full AsyncSequence
implementation).