Рекомендации по повышению производительности ASP.NET CoreASP.NET Core Performance Best Practices

Автор: Майк Роусос (Mike Rousos)By Mike Rousos

В этой статье приводятся рекомендации по обеспечению оптимальной производительности с помощью ASP.NET Core.This article provides guidelines for performance best practices with ASP.NET Core.

Агрессивный кэшCache aggressively

Кэширование рассматривается в нескольких частях этого документа.Caching is discussed in several parts of this document. Для получения дополнительной информации см. Кэширование ответов в ASP.NET Core.For more information, see Кэширование ответов в ASP.NET Core.

Общие сведения о путях к горячему кодуUnderstand hot code paths

В этом документе путь к горячему коду определяется как часто называемый путь к коду и где возникает большая часть времени выполнения.In this document, a hot code path is defined as a code path that is frequently called and where much of the execution time occurs. Пути с горячим кодом обычно ограничивают масштаб и производительность приложения и обсуждаются в нескольких частях этого документа.Hot code paths typically limit app scale-out and performance and are discussed in several parts of this document.

Избегайте блокирующих вызововAvoid blocking calls

ASP.NET Core приложения предназначены для одновременной обработки нескольких запросов.ASP.NET Core apps should be designed to process many requests simultaneously. Асинхронные интерфейсы API позволяют небольшому пулу потоков работать с тысячами одновременных запросов, не дожидаясь блокировки вызовов.Asynchronous APIs allow a small pool of threads to handle thousands of concurrent requests by not waiting on blocking calls. Вместо ожидания завершения длительной синхронной задачи поток может работать с другим запросом.Rather than waiting on a long-running synchronous task to complete, the thread can work on another request.

Распространенная проблема производительности в ASP.NET Core приложениях — блокировка вызовов, которые могут быть асинхронными.A common performance problem in ASP.NET Core apps is blocking calls that could be asynchronous. Многие синхронные блокирующие вызовы ведут к нехватке пула потоков и снижению времени отклика.Many synchronous blocking calls lead to Thread Pool starvation and degraded response times.

Не выполнять:Do not:

  • Блокировать асинхронное выполнение путем вызова Task. Wait или Task. Result.Block asynchronous execution by calling Task.Wait or Task.Result.
  • Получение блокировок в общих путях кода.Acquire locks in common code paths. ASP.NET Core приложения являются наиболее производительными при разработке архитектуры для параллельного выполнения кода.ASP.NET Core apps are most performant when architected to run code in parallel.
  • Вызовите Task. Run и немедленно ожидайте ее.Call Task.Run and immediately await it. ASP.NET Core уже выполняет код приложения в обычных потоках пула потоков, поэтому вызов задачи. выполнение приведет только к ненужному планированию пула потоков.ASP.NET Core already runs app code on normal Thread Pool threads, so calling Task.Run only results in extra unnecessary Thread Pool scheduling. Даже если запланированный код блокирует поток, Task. Run не препятствует этому.Even if the scheduled code would block a thread, Task.Run does not prevent that.

Выполните следующие действия.Do:

  • Сделайте неактивные пути к коду асинхронными.Make hot code paths asynchronous.
  • Асинхронно вызывайте API доступа к данным, ввода-вывода и длительные операции, если доступен асинхронный API.Call data access, I/O, and long-running operations APIs asynchronously if an asynchronous API is available. Не используйте Task. Run , чтобы сделать синхронный API асинхронным.Do not use Task.Run to make a synchronous API asynchronous.
  • Сделайте действия контроллера или Razor страницы асинхронными.Make controller/Razor Page actions asynchronous. Весь стек вызовов является асинхронным, чтобы воспользоваться преимуществами шаблонов async/await .The entire call stack is asynchronous in order to benefit from async/await patterns.

Профилировщик, например PerfView, можно использовать для поиска потоков, часто добавляемых в пул потоков.A profiler, such as PerfView, can be used to find threads frequently added to the Thread Pool. Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/StartСобытие указывает поток, добавленный в пул потоков.The Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start event indicates a thread added to the thread pool.

Возвращать большие коллекции на несколько страниц меньшего размераReturn large collections across multiple smaller pages

Веб-страница не должна загружать большие объемы данных одновременно.A webpage shouldn't load large amounts of data all at once. При возврате коллекции объектов определите, может ли это привести к проблемам с производительностью.When returning a collection of objects, consider whether it could lead to performance issues. Определите, могут ли при проектировании быть получены следующие неудовлетворительные результаты:Determine if the design could produce the following poor outcomes:

  • OutOfMemoryException или высокое потребление памятиOutOfMemoryException or high memory consumption
  • Нехватка пула потоков (см. следующие замечания по IAsyncEnumerable<T> )Thread pool starvation (see the following remarks on IAsyncEnumerable<T>)
  • Низкое время откликаSlow response times
  • Частая сборка мусораFrequent garbage collection

Добавьте разбиение на страницы, чтобы устранить предыдущие сценарии.Do add pagination to mitigate the preceding scenarios. При использовании параметров размера страницы и индекса страницы разработчики должны предпочесть проект, возвращающий частичный результат.Using page size and page index parameters, developers should favor the design of returning a partial result. Если требуется исчерпывающий результат, следует использовать разбивку на страницы для асинхронного заполнения пакетов результатов, чтобы избежать блокировки ресурсов сервера.When an exhaustive result is required, pagination should be used to asynchronously populate batches of results to avoid locking server resources.

Дополнительные сведения о разбиении на страницы и ограничении числа возвращаемых записей см. в следующих статьях:For more information on paging and limiting the number of returned records, see:

Возвращает IEnumerable<T> или IAsyncEnumerable<T>Return IEnumerable<T> or IAsyncEnumerable<T>

Возврат IEnumerable<T> из действия приводит к синхронной итерации коллекции с помощью сериализатора.Returning IEnumerable<T> from an action results in synchronous collection iteration by the serializer. В результате вызовы блокируются, что может стать причиной перегрузки пула потоков.The result is the blocking of calls and a potential for thread pool starvation. Чтобы избежать синхронного перечисления, используйте ToListAsync перед возвратом перечислимого.To avoid synchronous enumeration, use ToListAsync before returning the enumerable.

Начиная с ASP.NET Core 3,0, IAsyncEnumerable<T> можно использовать в качестве альтернативы для IEnumerable<T> перечисления в асинхронном режиме.Beginning with ASP.NET Core 3.0, IAsyncEnumerable<T> can be used as an alternative to IEnumerable<T> that enumerates asynchronously. Дополнительные сведения см. в разделе типы возвращаемых данных действий контроллера.For more information, see Controller action return types.

Сведение к минимальному выделению больших объектовMinimize large object allocations

Сборщик мусора .NET Core управляет выделением и освобождением памяти автоматически в ASP.NET Core приложениях.The .NET Core garbage collector manages allocation and release of memory automatically in ASP.NET Core apps. Автоматическая сборка мусора обычно означает, что разработчикам не нужно беспокоиться о том, как или когда освобождается память.Automatic garbage collection generally means that developers don't need to worry about how or when memory is freed. Тем не менее очистка объектов, на которые нет ссылок, занимает время ЦП, поэтому разработчики должны максимально сокращать выделение объектов в путях горячего кода.However, cleaning up unreferenced objects takes CPU time, so developers should minimize allocating objects in hot code paths. Сборка мусора особенно затратна на большие объекты (> 85 КБайт).Garbage collection is especially expensive on large objects (> 85 K bytes). Большие объекты хранятся в куче больших объектов и для очистки требуется полная сборка мусора (поколение 2).Large objects are stored on the large object heap and require a full (generation 2) garbage collection to clean up. В отличие от коллекций поколений 0 и поколения 1, сборка поколения 2 требует временной приостановки выполнения приложения.Unlike generation 0 and generation 1 collections, a generation 2 collection requires a temporary suspension of app execution. Частое выделение и освобождение больших объектов могут привести к нестабильной производительности.Frequent allocation and de-allocation of large objects can cause inconsistent performance.

РекомендацииRecommendations:

  • Рассмотрите возможность кэширования больших объектов, которые часто используются.Do consider caching large objects that are frequently used. Кэширование больших объектов предотвращает дорогостоящее выделение памяти.Caching large objects prevents expensive allocations.
  • Сделайте буферы пула с помощью аррайпул <T> для хранения больших массивов.Do pool buffers by using an ArrayPool<T> to store large arrays.
  • Не выделяйте большое количество кратковременных больших объектов в пути горячего кода.Do not allocate many, short-lived large objects on hot code paths.

Проблемы с памятью, например предшествующие, можно диагностировать путем просмотра статистики сборщика мусора (GC) в PerfView и изучения:Memory issues, such as the preceding, can be diagnosed by reviewing garbage collection (GC) stats in PerfView and examining:

  • Время остановки сборки мусора.Garbage collection pause time.
  • Процент времени процессора, затраченный на сборку мусора.What percentage of the processor time is spent in garbage collection.
  • Количество сборок мусора, которые являются поколением 0, 1 и 2.How many garbage collections are generation 0, 1, and 2.

Дополнительные сведения см. в разделе сбор мусора и производительность.For more information, see Garbage Collection and Performance.

Оптимизация доступа к данным и ввода-выводаOptimize data access and I/O

Взаимодействие с хранилищем данных и другими удаленными службами часто является наиболее медленной частью ASP.NET Core приложения.Interactions with a data store and other remote services are often the slowest parts of an ASP.NET Core app. Эффективное чтение и запись данных крайне важно для обеспечения высокой производительности.Reading and writing data efficiently is critical for good performance.

РекомендацииRecommendations:

  • Вызывайте все API доступа к данным в асинхронном режиме.Do call all data access APIs asynchronously.
  • Не извлекать больше данных, чем требуется.Do not retrieve more data than is necessary. Напишите запросы, возвращающие только те данные, которые необходимы для текущего HTTP-запроса.Write queries to return just the data that's necessary for the current HTTP request.
  • Рассмотрите возможность кэширования часто используемых данных, полученных из базы данных или удаленной службы, если это приемлемо для более неактуальных данных.Do consider caching frequently accessed data retrieved from a database or remote service if slightly out-of-date data is acceptable. В зависимости от сценария используйте MemoryCache или DistributedCache.Depending on the scenario, use a MemoryCache or a DistributedCache. Для получения дополнительной информации см. Кэширование ответов в ASP.NET Core.For more information, see Кэширование ответов в ASP.NET Core.
  • Сократите круговые обходов сети.Do minimize network round trips. Целью является получение необходимых данных в одном вызове, а не в нескольких вызовах.The goal is to retrieve the required data in a single call rather than several calls.
  • Не используйте запросы без отслеживания в Entity Framework Core при доступе к данным в целях только для чтения.Do use no-tracking queries in Entity Framework Core when accessing data for read-only purposes. EF Core могут более эффективно возвращать результаты запросов без отслеживания.EF Core can return the results of no-tracking queries more efficiently.
  • Выполните фильтрацию и агрегирование запросов LINQ (например, с помощью .Where .Select инструкций, или .Sum ), чтобы фильтрация выполнялась базой данных.Do filter and aggregate LINQ queries (with .Where, .Select, or .Sum statements, for example) so that the filtering is performed by the database.
  • Учтите, что EF Core разрешает некоторые операторы запросов на клиенте, что может привести к неэффективному выполнению запроса.Do consider that EF Core resolves some query operators on the client, which may lead to inefficient query execution. Дополнительные сведения см. в статье проблемы с производительностью оценки клиента.For more information, see Client evaluation performance issues.
  • Не Используйте проекции запросов к коллекциям, что может привести к выполнению запросов SQL N + 1.Do not use projection queries on collections, which can result in executing "N + 1" SQL queries. Дополнительные сведения см. в разделе Оптимизация коррелированных вложенных запросов.For more information, see Optimization of correlated subqueries.

Методы, которые могут повысить производительность в крупномасштабных приложениях, см. в статье Высокая производительность .See EF High Performance for approaches that may improve performance in high-scale apps:

Мы рекомендуем оценить влияние предыдущих высокопроизводительных подходов перед фиксацией базы кода.We recommend measuring the impact of the preceding high-performance approaches before committing the code base. Дополнительная сложность скомпилированных запросов может не отнять повышение производительности.The additional complexity of compiled queries may not justify the performance improvement.

Проблемы запросов можно обнаружить, просмотрев время, затраченное на доступ к данным с помощью Application Insights или с помощью средств профилирования.Query issues can be detected by reviewing the time spent accessing data with Application Insights or with profiling tools. В большинстве баз данных также доступна статистика, касающаяся часто выполняемых запросов.Most databases also make statistics available concerning frequently executed queries.

HTTP-соединения пула с ХттпклиентфакториPool HTTP connections with HttpClientFactory

Хотя HttpClient реализует IDisposable интерфейс, он предназначен для повторного использования.Although HttpClient implements the IDisposable interface, it's designed for reuse. Закрытые HttpClient экземпляры оставляют сокеты открытыми в TIME_WAIT состоянии в течение короткого периода времени.Closed HttpClient instances leave sockets open in the TIME_WAIT state for a short period of time. Если часто используется путь кода, который создает и уничтожает HttpClient объекты, приложение может вычерпать доступные сокеты.If a code path that creates and disposes of HttpClient objects is frequently used, the app may exhaust available sockets. Хттпклиентфактори был представлен в ASP.NET Core 2,1 в качестве решения этой проблемы.HttpClientFactory was introduced in ASP.NET Core 2.1 as a solution to this problem. Он обрабатывает подключения по протоколу HTTP для оптимизации производительности и надежности.It handles pooling HTTP connections to optimize performance and reliability.

РекомендацииRecommendations:

Быстрое отслеживание общих путей кодаKeep common code paths fast

Необходимо, чтобы весь код был быстрым.You want all of your code to be fast. Часто называемые путями кода наиболее важны для оптимизации.Frequently-called code paths are the most critical to optimize. Сюда входит следующее.These include:

  • Компоненты по промежуточного слоя в конвейере обработки запросов приложения, особенно по промежуточного слоя выполняются на раннем этапе конвейера.Middleware components in the app's request processing pipeline, especially middleware run early in the pipeline. Эти компоненты сильно влияют на производительность.These components have a large impact on performance.
  • Код, который выполняется для каждого запроса или несколько раз для каждого запроса.Code that's executed for every request or multiple times per request. Например, пользовательское ведение журнала, обработчики авторизации или инициализацию временных служб.For example, custom logging, authorization handlers, or initialization of transient services.

РекомендацииRecommendations:

Выполнение длительных задач за пределами HTTP-запросовComplete long-running Tasks outside of HTTP requests

Большинство запросов к ASP.NET Core приложению могут обрабатываться контроллером или моделью страницы, вызывающими необходимые службы и возвращающими ответ HTTP.Most requests to an ASP.NET Core app can be handled by a controller or page model calling necessary services and returning an HTTP response. Для некоторых запросов, в которых задействованы длительные задачи, лучше сделать весь процесс "запрос-ответ" асинхронным.For some requests that involve long-running tasks, it's better to make the entire request-response process asynchronous.

РекомендацииRecommendations:

  • Не дожидаться завершения длительных задач в рамках обычной обработки HTTP-запросов.Do not wait for long-running tasks to complete as part of ordinary HTTP request processing.
  • Рассмотрите возможность обработки долго выполняющихся запросов с помощью фоновых служб или вне процесса с помощью функции Azure.Do consider handling long-running requests with background services or out of process with an Azure Function. Завершение работы вне процесса особенно полезно для ресурсоемких задач.Completing work out-of-process is especially beneficial for CPU-intensive tasks.
  • SignalR Для асинхронной связи с клиентами используйте параметры связи в режиме реального времени, например.Do use real-time communication options, such as SignalR, to communicate with clients asynchronously.

Ресурсы клиента уменьшениеMinify client assets

ASP.NET Core приложения с комплексными внешними интерфейсами часто обслуживают множество файлов JavaScript, CSS или изображений.ASP.NET Core apps with complex front-ends frequently serve many JavaScript, CSS, or image files. Производительность запросов начальной загрузки можно улучшить следующим образом.Performance of initial load requests can be improved by:

  • Объединение, объединяющее несколько файлов в один.Bundling, which combines multiple files into one.
  • Минификация, что сокращает размер файлов, удаляя пробелы и комментарии.Minifying, which reduces the size of files by removing whitespace and comments.

РекомендацииRecommendations:

  • Используйте рекомендации по объединению и минификации, в которых упоминаются совместимые средства, и демонстрируется использование environment тега ASP.NET Core для работы с обеими Development Production средами и.Do use the bundling and minification guidelines, which mentions compatible tools and shows how to use ASP.NET Core's environment tag to handle both Development and Production environments.
  • Ознакомьтесь с другими сторонними инструментами, такими как веб- пакет, для комплексного управления клиентскими ресурсами.Do consider other third-party tools, such as Webpack, for complex client asset management.

Сжатие ответовCompress responses

Уменьшение размера ответа обычно значительно увеличивает скорость реагирования приложения.Reducing the size of the response usually increases the responsiveness of an app, often dramatically. Одним из способов уменьшения размера полезных данных является сжатие ответов приложения.One way to reduce payload sizes is to compress an app's responses. Дополнительные сведения см. в разделе сжатие ответов.For more information, see Response compression.

Использование последней версии ASP.NET CoreUse the latest ASP.NET Core release

Каждый новый выпуск ASP.NET Core включает улучшения производительности.Each new release of ASP.NET Core includes performance improvements. Оптимизация в .NET Core и ASP.NET Core означает, что более новые версии обычно более эффективны старые версии.Optimizations in .NET Core and ASP.NET Core mean that newer versions generally outperform older versions. Например, в .NET Core 2,1 добавлена поддержка скомпилированных регулярных выражений и выиграли из <T> span.For example, .NET Core 2.1 added support for compiled regular expressions and benefitted from Span<T>. В ASP.NET Core 2,2 добавлена поддержка HTTP/2.ASP.NET Core 2.2 added support for HTTP/2. ASP.NET Core 3,0 добавляет множество улучшений , которые уменьшают использование памяти и увеличивают пропускную способность.ASP.NET Core 3.0 adds many improvements that reduce memory usage and improve throughput. Если производительность является приоритетной, рассмотрите возможность обновления до текущей версии ASP.NET Core.If performance is a priority, consider upgrading to the current version of ASP.NET Core.

Уменьшение числа исключенийMinimize exceptions

Исключения должны быть редкими.Exceptions should be rare. Создание и перехват исключений происходит очень долго относительно других шаблонов потока кода.Throwing and catching exceptions is slow relative to other code flow patterns. Поэтому исключения не должны использоваться для управления нормальным выполнением программы.Because of this, exceptions shouldn't be used to control normal program flow.

РекомендацииRecommendations:

  • Не используйте генерацию или перехват исключений в качестве средства обычного потока программы, особенно в путях горячего кода.Do not use throwing or catching exceptions as a means of normal program flow, especially in hot code paths.
  • Включите в приложение логику для обнаружения и обработки условий, вызывающих исключение.Do include logic in the app to detect and handle conditions that would cause an exception.
  • Вызывайте или перехватите исключения для необычных или непредвиденных условий.Do throw or catch exceptions for unusual or unexpected conditions.

Средства диагностики приложений, такие как Application Insights, могут помочь в определении распространенных исключений в приложении, которое может повлиять на производительность.App diagnostic tools, such as Application Insights, can help to identify common exceptions in an app that may affect performance.

Производительность и надежностьPerformance and reliability

Следующие разделы содержат советы по повышению производительности и известным проблемам надежности и решениям.The following sections provide performance tips and known reliability problems and solutions.

Избегайте синхронного чтения или записи в тексте HttpRequest/HttpResponseAvoid synchronous read or write on HttpRequest/HttpResponse body

Все операции ввода-вывода в ASP.NET Core являются асинхронными.All I/O in ASP.NET Core is asynchronous. Серверы реализуют Stream интерфейс, который имеет как синхронные, так и асинхронные перегрузки.Servers implement the Stream interface, which has both synchronous and asynchronous overloads. Асинхронные объекты должны быть предпочтительнее, чтобы избежать блокирования потоков пула потоков.The asynchronous ones should be preferred to avoid blocking thread pool threads. Блокировка потоков может привести к нехватке пула потоков.Blocking threads can lead to thread pool starvation.

Не Выменяйте это: В следующем примере используется ReadToEnd .Do not do this: The following example uses the ReadToEnd. Он блокирует текущий поток для ожидания результата.It blocks the current thread to wait for the result. Это пример синхронизации через Async.This is an example of sync over async.

public class BadStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public ActionResult<ContosoData> Get()
    {
        var json = new StreamReader(Request.Body).ReadToEnd();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }
}

В приведенном выше коде Get синхронно считывает весь текст HTTP-запроса в память.In the preceding code, Get synchronously reads the entire HTTP request body into memory. Если клиент медленно отправляется, приложение выполняет синхронизацию асинхронно.If the client is slowly uploading, the app is doing sync over async. Приложение выполняет синхронизацию асинхронно, Kestrel так как не поддерживает синхронные операции чтения.The app does sync over async because Kestrel does NOT support synchronous reads.

Выполните следующие действия. В следующем примере используется ReadToEndAsync и не блокируется поток при чтении.Do this: The following example uses ReadToEndAsync and does not block the thread while reading.

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        var json = await new StreamReader(Request.Body).ReadToEndAsync();

        return JsonSerializer.Deserialize<ContosoData>(json);
    }

}

Приведенный выше код асинхронно считывает весь текст HTTP-запроса в память.The preceding code asynchronously reads the entire HTTP request body into memory.

Предупреждение

Если запрос имеет большой размер, чтение всего текста HTTP-запроса в память может привести к нехватке памяти.If the request is large, reading the entire HTTP request body into memory could lead to an out of memory (OOM) condition. Сбой может привести к отказу в обслуживании.OOM can result in a Denial Of Service. Дополнительные сведения см. в разделе избежание считывания текста большого запроса или тела ответа в память в этом документе.For more information, see Avoid reading large request bodies or response bodies into memory in this document.

Выполните следующие действия. Следующий пример полностью асинхронно использует текст запроса без буферизации:Do this: The following example is fully asynchronous using a non buffered request body:

public class GoodStreamReaderController : Controller
{
    [HttpGet("/contoso")]
    public async Task<ActionResult<ContosoData>> Get()
    {
        return await JsonSerializer.DeserializeAsync<ContosoData>(Request.Body);
    }
}

Приведенный выше код асинхронно десериализует текст запроса в объект C#.The preceding code asynchronously de-serializes the request body into a C# object.

Предпочитать Реадформасинк по запросу. FormPrefer ReadFormAsync over Request.Form

Используйте HttpContext.Request.ReadFormAsync вместо HttpContext.Request.Form.Use HttpContext.Request.ReadFormAsync instead of HttpContext.Request.Form. HttpContext.Request.Form может быть безопасно прочитано только со следующими условиями:HttpContext.Request.Form can be safely read only with the following conditions:

  • Форма была считана путем вызова функции ReadFormAsync иThe form has been read by a call to ReadFormAsync, and
  • Кэшированное значение формы считывается с помощью HttpContext.Request.FormThe cached form value is being read using HttpContext.Request.Form

Не Выменяйте это: В следующем примере используется HttpContext.Request.Form .Do not do this: The following example uses HttpContext.Request.Form. HttpContext.Request.Form использует синхронизацию асинхронно и может привести к нехватке пула потоков.HttpContext.Request.Form uses sync over async and can lead to thread pool starvation.

public class BadReadController : Controller
{
    [HttpPost("/form-body")]
    public IActionResult Post()
    {
        var form =  HttpContext.Request.Form;

        Process(form["id"], form["name"]);

        return Accepted();
    }

Выполните следующие действия. В следующем примере метод используется HttpContext.Request.ReadFormAsync для асинхронного чтения текста формы.Do this: The following example uses HttpContext.Request.ReadFormAsync to read the form body asynchronously.

public class GoodReadController : Controller
{
    [HttpPost("/form-body")]
    public async Task<IActionResult> Post()
    {
       var form = await HttpContext.Request.ReadFormAsync();

        Process(form["id"], form["name"]);

        return Accepted();
    }

Избегайте считывания текста крупного запроса или тела ответа в памятьAvoid reading large request bodies or response bodies into memory

В .NET каждое выделение объектов, превышающее 85 КБ, заканчивается в куче больших объектов (LOH).In .NET, every object allocation greater than 85 KB ends up in the large object heap (LOH). Большие объекты являются ресурсоемкими двумя способами:Large objects are expensive in two ways:

  • Стоимость выделения высока, так как память для вновь выделенного большого объекта должна быть сброшена.The allocation cost is high because the memory for a newly allocated large object has to be cleared. Среда CLR гарантирует, что память для всех вновь выделяемых объектов будет сброшена.The CLR guarantees that memory for all newly allocated objects is cleared.
  • LOH собирается вместе с остальной частью кучи.LOH is collected with the rest of the heap. Для LOH требуется полная сборка мусора или коллекция Gen2.LOH requires a full garbage collection or Gen2 collection.

Эта запись блога кратко описывает проблему:This blog post describes the problem succinctly:

При выделении большого объекта он помечается как объект Gen 2.When a large object is allocated, it's marked as Gen 2 object. Не Gen 0 как для небольших объектов.Not Gen 0 as for small objects. Последствия в том, что если в LOH исчерпана память, сборщик мусора очищает всю управляемую кучу, а не только LOH.The consequences are that if you run out of memory in LOH, GC cleans up the whole managed heap, not only LOH. Он очищает Gen 0, Gen 1 и Gen 2, включая LOH.So it cleans up Gen 0, Gen 1 and Gen 2 including LOH. Это называется полной сборкой мусора и является наиболее длительным сбором мусора.This is called full garbage collection and is the most time-consuming garbage collection. Для многих приложений это может быть приемлемым.For many applications, it can be acceptable. Но определенно не для высокопроизводительных веб-серверов, где несколько больших буферов памяти требуются для обработки среднего веб-запроса (чтение из сокета, распаковка, декодирование JSON & больше).But definitely not for high-performance web servers, where few big memory buffers are needed to handle an average web request (read from a socket, decompress, decode JSON & more).

Простое хранение большого запроса или текста ответа в одном byte[] или string :Naively storing a large request or response body into a single byte[] or string:

  • Может привести к быстрому запуску пространства в LOH.May result in quickly running out of space in the LOH.
  • Может вызвать проблемы с производительностью приложения из-за выполнения полных GC.May cause performance issues for the app because of full GCs running.

Работа с синхронным интерфейсом API обработки данныхWorking with a synchronous data processing API

При использовании сериализатора или отмены сериализатора, поддерживающего только синхронные операции чтения и записи (например, JSON.NET):When using a serializer/de-serializer that only supports synchronous reads and writes (for example, JSON.NET):

  • Асинхронная буферизация данных в память перед их передачей в сериализатор/de-сериализатор.Buffer the data into memory asynchronously before passing it into the serializer/de-serializer.

Предупреждение

Если запрос большой, это может привести к нехватке памяти («нехватки»).If the request is large, it could lead to an out of memory (OOM) condition. Сбой может привести к отказу в обслуживании.OOM can result in a Denial Of Service. Дополнительные сведения см. в разделе избежание считывания текста большого запроса или тела ответа в память в этом документе.For more information, see Avoid reading large request bodies or response bodies into memory in this document.

ASP.NET Core 3,0 используется по System.Text.Json умолчанию для СЕРИАЛИЗАЦИИ JSON.ASP.NET Core 3.0 uses System.Text.Json by default for JSON serialization. System.Text.Json:System.Text.Json:

  • асинхронно считывает и записывает JSON;Reads and writes JSON asynchronously.
  • оптимизирован для текста UTF-8;Is optimized for UTF-8 text.
  • предоставляет более высокую производительность, чем Newtonsoft.Json.Typically higher performance than Newtonsoft.Json.

Не хранить Ихттпконтекстакцессор. HttpContext в полеDo not store IHttpContextAccessor.HttpContext in a field

Ихттпконтекстакцессор. HttpContext возвращает HttpContext Активный запрос при доступе из потока запроса.The IHttpContextAccessor.HttpContext returns the HttpContext of the active request when accessed from the request thread. IHttpContextAccessor.HttpContext Не должен храниться в поле или переменной.The IHttpContextAccessor.HttpContext should not be stored in a field or variable.

Не Выменяйте это: В следующем примере объект сохраняется HttpContext в поле, а затем попытается его использовать позже.Do not do this: The following example stores the HttpContext in a field and then attempts to use it later.

public class MyBadType
{
    private readonly HttpContext _context;
    public MyBadType(IHttpContextAccessor accessor)
    {
        _context = accessor.HttpContext;
    }

    public void CheckAdmin()
    {
        if (!_context.User.IsInRole("admin"))
        {
            throw new UnauthorizedAccessException("The current user isn't an admin");
        }
    }
}

Приведенный выше код часто захватывает значение null или неверно HttpContext в конструкторе.The preceding code frequently captures a null or incorrect HttpContext in the constructor.

Выполните следующие действия. Следующий пример:Do this: The following example:

  • Сохраняет IHttpContextAccessor в поле.Stores the IHttpContextAccessor in a field.
  • Использует HttpContext поле в нужное время и проверяет наличие null .Uses the HttpContext field at the correct time and checks for null.
public class MyGoodType
{
    private readonly IHttpContextAccessor _accessor;
    public MyGoodType(IHttpContextAccessor accessor)
    {
        _accessor = accessor;
    }

    public void CheckAdmin()
    {
        var context = _accessor.HttpContext;
        if (context != null && !context.User.IsInRole("admin"))
        {
            throw new UnauthorizedAccessException("The current user isn't an admin");
        }
    }
}

Не обращаться к HttpContext из нескольких потоковDo not access HttpContext from multiple threads

HttpContextне является потокобезопасным.HttpContext is NOT thread-safe. Доступ HttpContext из нескольких потоков в параллельном режиме может привести к неопределенному поведению, такому как зависание, сбои и повреждение данных.Accessing HttpContext from multiple threads in parallel can result in undefined behavior such as hangs, crashes, and data corruption.

Не Выменяйте это: В следующем примере выполняется три параллельных запроса и записывается путь к входящему запросу до и после исходящего HTTP-запроса.Do not do this: The following example makes three parallel requests and logs the incoming request path before and after the outgoing HTTP request. Доступ к пути запроса осуществляется из нескольких потоков, которые могут быть параллельными.The request path is accessed from multiple threads, potentially in parallel.

public class AsyncBadSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        var query1 = SearchAsync(SearchEngine.Google, query);
        var query2 = SearchAsync(SearchEngine.Bing, query);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }       

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.", 
                                    HttpContext.Request.Path);
            searchResults = _searchService.Search(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", 
                                    HttpContext.Request.Path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", 
                             HttpContext.Request.Path);
        }

        return await searchResults;
    }

Выполните следующие действия. В следующем примере все данные из входящего запроса копируются перед выполнением трех параллельных запросов.Do this: The following example copies all data from the incoming request before making the three parallel requests.

public class AsyncGoodSearchController : Controller
{       
    [HttpGet("/search")]
    public async Task<SearchResults> Get(string query)
    {
        string path = HttpContext.Request.Path;
        var query1 = SearchAsync(SearchEngine.Google, query,
                                 path);
        var query2 = SearchAsync(SearchEngine.Bing, query, path);
        var query3 = SearchAsync(SearchEngine.DuckDuckGo, query, path);

        await Task.WhenAll(query1, query2, query3);

        var results1 = await query1;
        var results2 = await query2;
        var results3 = await query3;

        return SearchResults.Combine(results1, results2, results3);
    }

    private async Task<SearchResults> SearchAsync(SearchEngine engine, string query,
                                                  string path)
    {
        var searchResults = _searchService.Empty();
        try
        {
            _logger.LogInformation("Starting search query from {path}.",
                                   path);
            searchResults = await _searchService.SearchAsync(engine, query);
            _logger.LogInformation("Finishing search query from {path}.", path);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed query from {path}", path);
        }

        return await searchResults;
    }

Не используйте HttpContext после завершения запросаDo not use the HttpContext after the request is complete

HttpContext допустимо только при условии, что в конвейере ASP.NET Core имеется активный HTTP-запрос.HttpContext is only valid as long as there is an active HTTP request in the ASP.NET Core pipeline. Весь конвейер ASP.NET Core является асинхронной цепочкой делегатов, выполняющих каждый запрос.The entire ASP.NET Core pipeline is an asynchronous chain of delegates that executes every request. После Task завершения возврата из этой цепочки HttpContext перезапускается.When the Task returned from this chain completes, the HttpContext is recycled.

Не Выменяйте это: В следующем примере используется async void , после чего HTTP-запрос завершается при await достижении первого.Do not do this: The following example uses async void which makes the HTTP request complete when the first await is reached:

  • Это всегда является неправильной практикой в ASP.NET Core приложениях.Which is ALWAYS a bad practice in ASP.NET Core apps.
  • Обращается к компоненту HttpResponse после завершения HTTP-запроса.Accesses the HttpResponse after the HTTP request is complete.
  • Завершается сбоем процесса.Crashes the process.
public class AsyncBadVoidController : Controller
{
    [HttpGet("/async")]
    public async void Get()
    {
        await Task.Delay(1000);

        // The following line will crash the process because of writing after the 
        // response has completed on a background thread. Notice async void Get()

        await Response.WriteAsync("Hello World");
    }
}

Выполните следующие действия. В следующем примере возвращается Task к платформе, поэтому HTTP-запрос не завершается до завершения действия.Do this: The following example returns a Task to the framework, so the HTTP request doesn't complete until the action completes.

public class AsyncGoodTaskController : Controller
{
    [HttpGet("/async")]
    public async Task Get()
    {
        await Task.Delay(1000);

        await Response.WriteAsync("Hello World");
    }
}

Не захватывать HttpContext в фоновых потокахDo not capture the HttpContext in background threads

Не Выменяйте это: В следующем примере показано, как захватывается замыкание HttpContext из Controller Свойства.Do not do this: The following example shows a closure is capturing the HttpContext from the Controller property. Это неплохая практика, поскольку рабочий элемент может:This is a bad practice because the work item could:

  • Выполнение за пределами области запроса.Run outside of the request scope.
  • Попытка прочитать ошибку HttpContext .Attempt to read the wrong HttpContext.
[HttpGet("/fire-and-forget-1")]
public IActionResult BadFireAndForget()
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        var path = HttpContext.Request.Path;
        Log(path);
    });

    return Accepted();
}

Выполните следующие действия. Следующий пример:Do this: The following example:

  • Копирует данные, необходимые в фоновой задаче, во время запроса.Copies the data required in the background task during the request.
  • Не ссылается на что-либо из контроллера.Doesn't reference anything from the controller.
[HttpGet("/fire-and-forget-3")]
public IActionResult GoodFireAndForget()
{
    string path = HttpContext.Request.Path;
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        Log(path);
    });

    return Accepted();
}

Фоновые задачи должны быть реализованы как размещенные службы.Background tasks should be implemented as hosted services. Дополнительные сведения см. в статье Background tasks with hosted services in ASP.NET Core (Фоновые задачи с размещенными службами в ASP.NET Core).For more information, see Background tasks with hosted services.

Не захватывать службы, внедренные в контроллеры в фоновых потокахDo not capture services injected into the controllers on background threads

Не Выменяйте это: В следующем примере показано, как захватывается замыкание DbContext из Controller параметра Action.Do not do this: The following example shows a closure is capturing the DbContext from the Controller action parameter. Это неплохой подход.This is a bad practice. Рабочий элемент может выполняться вне области запроса.The work item could run outside of the request scope. ContosoDbContextОбласть ограничена запросом, что приводит к возникновению ObjectDisposedException .The ContosoDbContext is scoped to the request, resulting in an ObjectDisposedException.

[HttpGet("/fire-and-forget-1")]
public IActionResult FireAndForget1([FromServices]ContosoDbContext context)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        context.Contoso.Add(new Contoso());
        await context.SaveChangesAsync();
    });

    return Accepted();
}

Выполните следующие действия. Следующий пример:Do this: The following example:

  • Внедряет объект, IServiceScopeFactory чтобы создать область в фоновом рабочем элементе.Injects an IServiceScopeFactory in order to create a scope in the background work item. IServiceScopeFactory является Singleton-классом.IServiceScopeFactory is a singleton.
  • Создает новую область внедрения зависимостей в фоновом потоке.Creates a new dependency injection scope in the background thread.
  • Не ссылается на что-либо из контроллера.Doesn't reference anything from the controller.
  • Не захватывает ContosoDbContext из входящего запроса.Doesn't capture the ContosoDbContext from the incoming request.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                    serviceScopeFactory)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();

            context.Contoso.Add(new Contoso());

            await context.SaveChangesAsync();                                        
        }
    });

    return Accepted();
}

Выделенный ниже код:The following highlighted code:

  • Создает область в течение времени существования фоновой операции и разрешает службы из нее.Creates a scope for the lifetime of the background operation and resolves services from it.
  • Использует ContosoDbContext из правильной области.Uses ContosoDbContext from the correct scope.
[HttpGet("/fire-and-forget-3")]
public IActionResult FireAndForget3([FromServices]IServiceScopeFactory 
                                    serviceScopeFactory)
{
    _ = Task.Run(async () =>
    {
        await Task.Delay(1000);

        using (var scope = serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<ContosoDbContext>();

            context.Contoso.Add(new Contoso());

            await context.SaveChangesAsync();                                        
        }
    });

    return Accepted();
}

Не изменяйте код состояния или заголовки после начала текста ответаDo not modify the status code or headers after the response body has started

ASP.NET Core не замещает текст HTTP-ответа.ASP.NET Core does not buffer the HTTP response body. При первом написании ответа:The first time the response is written:

  • Заголовки отправляются клиенту вместе с этим фрагментом текста.The headers are sent along with that chunk of the body to the client.
  • Больше нельзя изменять заголовки ответа.It's no longer possible to change response headers.

Не Выменяйте это: Следующий код пытается добавить заголовки ответа после того, как ответ уже запущен:Do not do this: The following code tries to add response headers after the response has already started:

app.Use(async (context, next) =>
{
    await next();

    context.Response.Headers["test"] = "test value";
});

В приведенном выше коде context.Response.Headers["test"] = "test value"; вызовет исключение, если в next() ответ записывается.In the preceding code, context.Response.Headers["test"] = "test value"; will throw an exception if next() has written to the response.

Выполните следующие действия. В следующем примере проверяется, начался ли HTTP-ответ перед изменением заголовков.Do this: The following example checks if the HTTP response has started before modifying the headers.

app.Use(async (context, next) =>
{
    await next();

    if (!context.Response.HasStarted)
    {
        context.Response.Headers["test"] = "test value";
    }
});

Выполните следующие действия. В следующем примере используется HttpResponse.OnStarting для установки заголовков перед тем, как заголовки ответа сбрасываются на клиент.Do this: The following example uses HttpResponse.OnStarting to set the headers before the response headers are flushed to the client.

Проверка того, что ответ не запущен, позволяет регистрировать обратный вызов, который будет вызываться непосредственно перед заголовком ответа.Checking if the response has not started allows registering a callback that will be invoked just before response headers are written. Проверяется, не начался ли ответ:Checking if the response has not started:

  • Предоставляет возможность добавлять или переопределять заголовки по времени.Provides the ability to append or override headers just in time.
  • Не требует знания следующего по промежуточного слоя в конвейере.Doesn't require knowledge of the next middleware in the pipeline.
app.Use(async (context, next) =>
{
    context.Response.OnStarting(() =>
    {
        context.Response.Headers["someheader"] = "somevalue";
        return Task.CompletedTask;
    });

    await next();
});

Не вызывайте Next (), если вы уже начали записывать в текст ответа.Do not call next() if you have already started writing to the response body

Компоненты должны вызываться, только если они могут обрабатывать ответ и управлять им.Components only expect to be called if it's possible for them to handle and manipulate the response.

Использование внутрипроцессного размещения с IISUse In-process hosting with IIS

При внутрипроцессном размещении приложение ASP.NET Core выполняется в том же процессе, что и рабочий процесс IIS.Using in-process hosting, an ASP.NET Core app runs in the same process as its IIS worker process. Внутрипроцессный процесс обеспечивает повышенную производительность при наведении вне процесса, так как запросы не передаются через адаптер замыкания на себя.In-process hosting provides improved performance over out-of-process hosting because requests aren't proxied over the loopback adapter. Адаптер замыкания на себя — это сетевой интерфейс, который возвращает исходящий сетевой трафик обратно на тот же компьютер.The loopback adapter is a network interface that returns outgoing network traffic back to the same machine. IIS обрабатывает управление процессом с помощью службы активации процессов Windows (WAS).IIS handles process management with the Windows Process Activation Service (WAS).

По умолчанию в проектах используется модель внутрипроцессного размещения в ASP.NET Core 3,0 и более поздних версиях.Projects default to the in-process hosting model in ASP.NET Core 3.0 and later.

Дополнительные сведения см. в разделе Host ASP.NET Core в Windows со службами IIS .For more information, see Host ASP.NET Core on Windows with IIS