'AsyncEnumerableReader' достиг настроенного максимального размера буфера при перечислении значения
Я использую метод async/await, возвращающий IAsyncEnumerable<> для извлечения строк из базы данных SQL Server и предоставления их через интерфейс Web API .Net Core 3.0. Он отлично работает, пока я не превысил 8192 строки. Он не работает для строк за пределами этой точки. Я пробовал 3 разные таблицы, каждая с очень разными размерами строк. Каждый раз сбой происходит после получения 8192 строк. Вот ошибка, которую я вижу:
An unhandled exception occurred while processing the request.
InvalidOperationException: 'AsyncEnumerableReader' reached the configured maximum size of the buffer when enumerating a value of type 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]'. This limit is in place to prevent infinite streams of 'IAsyncEnumerable<>' from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]' into a list rather than increasing the limit.
Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase<T>(IAsyncEnumerable<object> value)
Stack Query Cookies Headers Routing
InvalidOperationException: 'AsyncEnumerableReader' reached the configured maximum size of the buffer when enumerating a value of type 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]'. This limit is in place to prevent infinite streams of 'IAsyncEnumerable<>' from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]' into a list rather than increasing the limit.
Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase<T>(IAsyncEnumerable<object> value)
Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase<T>(IAsyncEnumerable<object> value)
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, IAsyncEnumerable<object> asyncEnumerable)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Show raw exception details
System.InvalidOperationException: 'AsyncEnumerableReader' reached the configured maximum size of the buffer when enumerating a value of type 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]'. This limit is in place to prevent infinite streams of 'IAsyncEnumerable<>' from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting 'Microsoft.EntityFrameworkCore.TestDatabase.TestDatabaseDbSet`1[TestNamespace.TestClass]' into a list rather than increasing the limit.
at Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase[T](IAsyncEnumerable`1 value)
at Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadTestDatabase[T](IAsyncEnumerable`1 value)
at Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, IAsyncEnumerable`1 asyncEnumerable)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
reached the configured maximum size of the buffer when enumerating a value of type Microsoft.EntityFrameworkCore DbSet This limit is in place to prevent infinite streams from continuing indefinitely. If this is not a programming mistake, consider ways to reduce the collection size, or consider manually converting Microsoft.EntityFrameworkCore DbSet into a list rather than increasing the limit.
Microsoft.EntityFrameworkCore.DbSet AsyncEnumerable
5 ответов
Что происходит
Предел исходит от свойства MvcOptions.MaxIAsyncEnumerableBufferLimit:
Получает или задает максимальное количество записей IAsyncEnumerable, которое ObjectResultExecutor будет буферизовать.
Когда Value является экземпляром IAsyncEnumerable, ObjectResultExecutor будет охотно читать перечисление и добавлять его в синхронную коллекцию перед вызовом выбранного средства форматирования. Это свойство определяет максимальное количество записей, которое исполнитель может буферизовать.
(...)
По умолчанию 8192.
Исправить
На мой взгляд, от лучшего к худшему.
A. (Предпочтительно) Не возвращайте так много записей
Я бы рекомендовал не возвращать так много записей из веб-API.
Пейджинг - это один из способов работы с большим количеством записей. Вы можете проверить Учебник: добавление сортировки, фильтрации и разбиения на страницы - ASP.NET MVC с EF Core.
Б. Отрегулируйте предел
MvcOptions.MaxIAsyncEnumerableBufferLimit
можно установить другое значение.
C. Не используйте IAsyncEnumerable
Вы можете вернуться IEnumerable
- он сможет обрабатывать большее количество записей.
В ASP.NET Core 5 действительно экземпляры типа
IAsyncEnumerable
были обработаны путем одновременной буферизации последовательности в памяти и форматирования буферизованной коллекции. Это объясняет, почему вы видите это исключение.
Однако с ASP.NET Core 6.0 этого больше не будет!
В ASP.NET Core 6 при форматировании с использованием System.Text.Json MVC больше не буферизует экземпляры IAsyncEnumerable. Вместо этого MVC полагается на поддержку, которую System.Text.Json добавил для этих типов ([ссылка][1])
См. Раздел Разъяснение того, как IAsyncEnumerable работает с веб-API ASP.NET, для получения дополнительных сведений о том, как использовать IAsyncEnumerable.
В моем случае я столкнулся с этой проблемой в контексте миграции конечных точек odata с .net core 2.2 на .net core 3.1. В качестве решения я наткнулся на статью с предложенным решением . Короче говоря, мы должны создать собственный метод расширения ToQueryable(), который не реализует интерфейс IAsyncEnumerable, и использовать его вместо существующего AsQueryable().
Он работает как шарм без дополнительной фильтрации по строке запроса. Но когда мы попытались отправить $top=3 в запросе фильтрации, мы получили ошибку: InvalidCastException: невозможно преобразовать объект типа
Queryable
печатать
System.Linq.IOrderedQueryable
'. Решением было обновить код, приведенный в упомянутой выше статье, путем замены IQueryable на IOrderedQueryable. В результате у меня получилось:
public static partial class CustomOrderedQueryable
{
public static IEnumerable<T> ToEnumerable<T>(this IEnumerable<T> source)
{
foreach (var item in source)
yield return item;
}
}
public static partial class CustomOrderedQueryable
{
public static IOrderedQueryable<T> ToQueryable<T>(this IQueryable<T> source)
=> new OrderedQueryable<T>(new QueryProvider(source.Provider), source.Expression);
class OrderedQueryable<T> : IOrderedQueryable<T>
{
internal OrderedQueryable(IQueryProvider provider, Expression expression)
{
Provider = provider;
Expression = expression;
}
public Type ElementType => typeof(T);
public Expression Expression { get; }
public IQueryProvider Provider { get; }
public IEnumerator<T> GetEnumerator() => Provider.Execute<IEnumerable<T>>(Expression)
.ToEnumerable().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
class QueryProvider : IQueryProvider
{
private readonly IQueryProvider source;
internal QueryProvider(IQueryProvider source) => this.source = source;
public IQueryable CreateQuery(Expression expression)
{
var query = source.CreateQuery(expression);
return (IQueryable)Activator.CreateInstance(
typeof(OrderedQueryable<>).MakeGenericType(query.ElementType),
this, query.Expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
=> new OrderedQueryable<TElement>(this, expression);
public object Execute(Expression expression) => source.Execute(expression);
public TResult Execute<TResult>(Expression expression) => source.Execute<TResult>(expression);
}
}
Сообщение об исключении и описание прекрасно описывают ситуацию. Вы читаете (исключительно большой) объем данных в буфер, и фреймворк предупреждает вас, что вы, вероятно, делаете то, чего не ожидаете, или запросили слишком много информации.
Поскольку вы используете WEBAPI, вас предупреждают, потому что цель WEBAPI и веб-сайтов в целом - иметь низкое время отклика, а необходимость сбора данных по 8K+ записям обычно этого не позволяет. При этом мы не знаем фактического варианта использования вашего приложения, и вам может быть все равно, но все же фреймворк пытается помочь вам поступать "правильно".
Независимо от вашего уровня опыта, отказ от чтения / интерпретации сообщений об ошибках - это легкая ловушка. Вместо этого подумайте, что они означают, и, если вы их не понимаете, перечитайте и исследуйте, пока не поймете. Мы прошли долгий путь со времен медитаций гуру, поэтому не принимайте эту полезную информацию как должное.
TL;DR Вы читаете слишком много данных за раз. Найдите способ разбить данные на более мелкие части, чтобы вернуть их клиенту. Меньший размер страницы - отличное место для начала.
Действительно, в ASP.NET Core 5 экземпляры типа обрабатывались путем одновременной буферизации последовательности в памяти и форматирования буферизованной коллекции. Это объясняет, почему вы видите это исключение.
Однако с ASP.NET Core 6.0 этого больше не будет!
В ASP.NET Core 6 при форматировании с использованием System.Text.Json MVC больше не буферизует экземпляры IAsyncEnumerable. Вместо этого MVC полагается на поддержку, которую System.Text.Json добавил для этих типов (ссылка)
Релиз для ASP.NET Core 6 запланирован на ноябрь 2021 года (для справки). Уже сейчас можно протестировать новое поведение с помощью предварительных версий. Я успешно протестировал следующий код, используя предварительную версию 6.0.100-preview.6.21355.2. Код создает бесконечный поток целых чисел и возвращает его через контроллер, используя
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace dot_net_api_streaming.Controllers
{
[ApiController]
[Route("[controller]")]
public class LoopController : ControllerBase
{
[HttpGet]
public IAsyncEnumerable<int> Get()
{
return GetInfiniteInts();
}
private async IAsyncEnumerable<int> GetInfiniteInts()
{
int index = 0;
while (true)
yield return index++;
}
}
}
* Пожалуйста, имейте это в виду, экспериментируя с моим кодом, чтобы ваша машина не вылетела из строя :)