twilio/twilio-csharp

Feature request: Enable usage of `IAsyncEnumerable<T>` for e. g. `ResourceSet<Room>`

Eagle3386 opened this issue · 2 comments

Issue Summary

Besides PR #589 which remains open for about half a year now, I'd like to ask for enhancement to the Video API so that I could do this:

Steps to Reproduce

  1. Implement a method that queries the Video API for available rooms like below.
  2. Try to consume that method or at least make the compiler accept that method's content.

Code Snippet

// [... code left out for brevity ...]
using static Twilio.Rest.Video.V1.Room.ParticipantResource;
using static Twilio.Rest.Video.V1.RoomResource;
// [... code left out for brevity ...]
public class TwilioService
{
  // [... code left out for brevity ...]
  public async IAsyncEnumerable<Room> GetRoomsAsync()
  {
    var roomTasks = (await ReadAsync().ConfigureAwait(false)).Select(room => Task.Run(async () => new Room
    {
      Name             = room.UniqueName,
      ParticipantCount = (await ReadAsync(room.Sid, StatusEnum.Connected).ConfigureAwait(false)).Count(),
      ParticipantLimit = room.MaxParticipants ?? 0
    }));
    await foreach (var room in roomTasks.ConfigureAwait(false))
    {
      yield return room;
    }
  }
}
// [... code left out for brevity ...]
public class Room
{
  public string Id { get; init; } = null!;
  public string Name { get; init; } = null!;
  public int ParticipantCount { get; set; }
  public int ParticipantLimit { get; init; }
}

Exception/Log

Error Code:  CS8415
Description: Asynchronous foreach statement cannot operate on variables of type 'IEnumerable<Task<Room>>' because
             'IEnumerable<Task<Room>>' does not contain a public instance or extension definition for
             'GetAsyncEnumerator'. Did you mean 'foreach' rather than 'await foreach'?

Technical details:

  • twilio-csharp version: 5.75.0
  • csharp version: 10 (.NET 6)

This issue has been added to our internal backlog to be prioritized. Pull requests and +1s on the issue summary will help it move up the backlog.

FWIW: using NuGet package System.Linq.Async, the initial code snippet can be simplified to this:

public async IAsyncEnumerable<Room> GetRoomsAsync([EnumeratorCancellation] CancellationToken token = default)
{
  var rooms = await RoomResource.ReadAsync().ConfigureAwait(false);
  await foreach (var room in rooms.ToAsyncEnumerable().WithCancellation(token).ConfigureAwait(false))
  {
    var participants = await ParticipantResource.ReadAsync(room.Sid, StatusEnum.Connected).ConfigureAwait(false);

    yield return new Room
    {
      Name             = room.UniqueName,
      ParticipantCount = participants.Count(),
      ParticipantLimit = room.MaxParticipants ?? 0
    };
  }
}

Furthermore, I'd like to suggest that the Room type exposes an additional int property like CurrentParticipants, thereby delivering 2 advantages at once:

  1. Enable immediate discovery of already active participants within the room (CurrentParticipants > 0).
  2. Prevent 1 additionally (awaited) round-trip to ParticipantResource.ReadAsync().