IAsyncEnumerable, yielding from event handler

Viewed 672

I'm trying to wrap an asynchronous subscription API based on events with an API based on IAsyncEnumerable. Basically along the lines of:

async IAsyncEnumerable<string> ReadAll() 
{
    var reader = new EventBasedReader();
    reader.OnRead => (_, args) => yield return e.Message;
    reader.Start();
    await reader.WaitUntilAllRead();
}

However this doesn't work because it's the event handler that yields, and this isn't allowed. Is there another way I can write this to make it work as an IAsyncEnumerable?

1 Answers

Wrap an asynchronous subscription API based on events with an API based on IAsyncEnumerable.

Those two are not directly compatible. Events are push-based, and enumerables (including async enumerables) are pull-based.

In order to cross that divide, you need a buffer - some place to hold the event data as it is pushed to you but before the downstream code has pulled it.

I recommend using Channels for buffers. If your use case allows it, you could use an unbounded channel:

IAsyncEnumerable<string> ReadAll() 
{
  var reader = new EventBasedReader();
  var buffer = Channel.CreateUnbounded<string>();
  reader.OnRead = async (_, args) => await buffer.Writer.WriteAsync(e.Message);
  reader.Start();
  CompleteBufferWhenEventsAreDone();
  return buffer.Reader.ReadAllAsync();

  async void CompleteBufferWhenEventsAreDone()
  {
    await reader.WaitUntilAllRead();
    buffer.Writer.TryComplete();
  }
}
Related