Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IAsyncEnumerable, yielding from event handler

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?

like image 810
Barguast Avatar asked Dec 23 '22 17:12

Barguast


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();
  }
}
like image 141
Stephen Cleary Avatar answered Jan 06 '23 18:01

Stephen Cleary