In .net 6 IAsyncEnumerable handling for MVC was changed when using System.Text.Json:
MVC no longer buffers IAsyncEnumerable instances. Instead, MVC relies on the support that System.Text.Json added for these types.
It means that controller will start sending output immediately and a client may start process it as it receives chunks of the response.
Here is an example with help of new minimal API:
Endpoint binding:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// this endpoint return IAsyncEnumerable<TestData>
app.MapGet("/asyncEnumerable/{count}", (int count) => GetLotsOfDataAsyncEnumerable(count));
// and this one returns Task<IEnumerable<TestData>>
app.MapGet("/{count}", async (int count) => await GetLotsOfDataAsync(count));
app.Run();
Controller methods:
async Task<IEnumerable<TestData>> GetLotsOfDataAsync(int count)
{
var list = new List<TestData>();
for (int i = 0; i < count; i++)
{
await Task.Delay(10);
list.Add(new TestData($"{i}"));
}
return list;
}
async IAsyncEnumerable<TestData> GetLotsOfDataAsyncEnumerable(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(10);
yield return new TestData($"{i}");
}
}
class TestData
{
public string Field { get; }
public TestData(string field)
{
Field = field;
}
}
count path variable allows to control how many data we want to retrieve in a single call.
I've tested it with curl command on a windows machine (here is the answer explaining how to measure performance with curl), results for 100 entries:
/100 /asyncEnumerable/100
time_namelookup: 0.000045s 0.000034s
time_connect: 0.000570s 0.000390s
time_appconnect: 0.000000s 0.000000s
time_pretransfer: 0.000648s 0.000435s
time_redirect: 0.000000s 0.000000s
time_starttransfer: 1.833341s 0.014880s
---------------------
time_total: 1.833411s 1.673477s
Important here to see is time_starttransfer, from curl manpage
The time, in seconds, it took from the start until the first byte was just about to be transferred. This includes time_pretransfer and also the time the server needed to calculate the result.
As you can see /asyncEnumerable endpoint started responding instantly, of course, clients of such endpoints have to be aware of such behavior to make good use of it.
Here how it looks in a cmdline:
