Повторное использование объекта с ObjectPool в ASP.NET Core
Günther Foidl, Стив Гордон и Сэмсон Амуго
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 9 этой статьи.
Microsoft.Extensions.ObjectPool является частью инфраструктуры ASP.NET Core, которая поддерживает сохранение группы объектов в памяти для повторного использования, а не позволяет объектам собирать мусор. Все статические и экземплярные методы в Microsoft.Extensions.ObjectPool
потокобезопасны.
Приложения могут использовать пул объектов, если управляемые объекты:
- Затраты на выделение и инициализацию.
- Представляет ограниченный ресурс.
- Используется прогнозируемо и часто.
Например, платформа ASP.NET Core использует пул объектов в некоторых местах для повторного использования StringBuilder экземпляров. StringBuilder
выделяет и управляет собственными буферами для хранения символьных данных. ASP.NET Core регулярно используется StringBuilder
для реализации функций, а их повторное использование обеспечивает преимущество производительности.
Пул объектов не всегда повышает производительность:
- Если стоимость инициализации объекта высока, обычно это медленнее, чтобы получить объект из пула.
- Объекты, управляемые пулом, не выделяются без выделения пула.
Используйте пул объектов только после сбора данных о производительности с помощью реалистичных сценариев для приложения или библиотеки.
ПРИМЕЧАНИЕ. ObjectPool не помещает ограничение на количество выделенных объектов, оно устанавливает ограничение на количество сохраненных объектов.
Основные понятия ObjectPool
Когда DefaultObjectPoolProvider используется и T
реализуется IDisposable
:
- Элементы, которые не возвращаются в пул, будут удалены.
- Когда пул удаляется di, все элементы в пуле удаляются.
ПРИМЕЧАНИЕ. После удаления пула:
- Вызов
Get
вызывает исключениеObjectDisposedException
. - Вызов
Return
удаляет заданный элемент.
Важные ObjectPool
типы и интерфейсы:
- ObjectPool<T> : абстракция базового пула объектов. Используется для получения и возврата объектов.
- PooledObjectPolicy<T> : реализуйте эту реализацию, чтобы настроить создание объекта и способ ее сброса при возврате в пул. Это можно передать в пул объектов, созданный напрямую.
- IResettable : автоматически сбрасывает объект при возвращении в пул объектов.
Объект ObjectPool можно использовать в приложении несколькими способами:
- Создание экземпляра пула.
- Регистрация пула в внедрении зависимостей (DI) в качестве экземпляра.
- Регистрация
ObjectPoolProvider<>
в di и его использование в качестве фабрики.
Использование ObjectPool
Вызов Get для получения объекта и Return возврата объекта. Нет необходимости возвращать каждый объект. Если объект не возвращается, он будет собирать мусор.
Пример ObjectPool
Следующий код:
- Добавляется
ObjectPoolProvider
в контейнер внедрения зависимостей (DI). IResettable
Реализует интерфейс для автоматического очистки содержимого буфера при возвращении в пул объектов.
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.ObjectPool;
using System.Security.Cryptography;
var builder = WebApplication.CreateBuilder(args);
builder.Services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
builder.Services.TryAddSingleton<ObjectPool<ReusableBuffer>>(serviceProvider =>
{
var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
var policy = new DefaultPooledObjectPolicy<ReusableBuffer>();
return provider.Create(policy);
});
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
// return the SHA256 hash of a word
// https://localhost:7214/hash/SamsonAmaugo
app.MapGet("/hash/{name}", (string name, ObjectPool<ReusableBuffer> bufferPool) =>
{
var buffer = bufferPool.Get();
try
{
// Set the buffer data to the ASCII values of a word
for (var i = 0; i < name.Length; i++)
{
buffer.Data[i] = (byte)name[i];
}
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(buffer.Data.AsSpan(0, name.Length), hash);
return "Hash: " + Convert.ToHexString(hash);
}
finally
{
// Data is automatically reset because this type implemented IResettable
bufferPool.Return(buffer);
}
});
app.Run();
public class ReusableBuffer : IResettable
{
public byte[] Data { get; } = new byte[1024 * 1024]; // 1 MB
public bool TryReset()
{
Array.Clear(Data);
return true;
}
}
ПРИМЕЧАНИЕ. Если тип T
в пуле не реализуется IResettable
, пользователь PooledObjectPolicy<T>
может использоваться для сброса состояния объектов перед возвратом в пул.
Microsoft.Extensions.ObjectPool является частью инфраструктуры ASP.NET Core, которая поддерживает сохранение группы объектов в памяти для повторного использования, а не позволяет объектам собирать мусор. Все статические и экземплярные методы в Microsoft.Extensions.ObjectPool
потокобезопасны.
Приложения могут использовать пул объектов, если управляемые объекты:
- Затраты на выделение и инициализацию.
- Представляет ограниченный ресурс.
- Используется прогнозируемо и часто.
Например, платформа ASP.NET Core использует пул объектов в некоторых местах для повторного использования StringBuilder экземпляров. StringBuilder
выделяет и управляет собственными буферами для хранения символьных данных. ASP.NET Core регулярно используется StringBuilder
для реализации функций, а их повторное использование обеспечивает преимущество производительности.
Пул объектов не всегда повышает производительность:
- Если стоимость инициализации объекта высока, обычно это медленнее, чтобы получить объект из пула.
- Объекты, управляемые пулом, не выделяются без выделения пула.
Используйте пул объектов только после сбора данных о производительности с помощью реалистичных сценариев для приложения или библиотеки.
ПРИМЕЧАНИЕ. ObjectPool не помещает ограничение на количество выделенных объектов, оно устанавливает ограничение на количество сохраненных объектов.
Основные понятия
Когда DefaultObjectPoolProvider используется и T
реализуется IDisposable
:
- Элементы, которые не возвращаются в пул, будут удалены.
- Когда пул удаляется di, все элементы в пуле удаляются.
ПРИМЕЧАНИЕ. После удаления пула:
- Вызов
Get
вызывает исключениеObjectDisposedException
. - Вызов
Return
удаляет заданный элемент.
Важные ObjectPool
типы и интерфейсы:
- ObjectPool<T> : абстракция базового пула объектов. Используется для получения и возврата объектов.
- PooledObjectPolicy<T> : реализуйте эту реализацию, чтобы настроить создание объекта и способ ее сброса при возврате в пул. Это можно передать в пул объектов, который создается напрямую или
- Create : выступает в качестве фабрики для создания пулов объектов.
- IResettable : автоматически сбрасывает объект при возвращении в пул объектов.
Объект ObjectPool можно использовать в приложении несколькими способами:
- Создание экземпляра пула.
- Регистрация пула в внедрении зависимостей (DI) в качестве экземпляра.
- Регистрация
ObjectPoolProvider<>
в di и его использование в качестве фабрики.
Использование ObjectPool
Вызов Get для получения объекта и Return возврата объекта. Нет необходимости возвращать каждый объект. Если объект не возвращается, он будет собирать мусор.
Пример ObjectPool
Следующий код:
- Добавляется
ObjectPoolProvider
в контейнер внедрения зависимостей (DI). - Добавляет и настраивает
ObjectPool<StringBuilder>
контейнер DI. BirthdayMiddleware
Добавляет объект .
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.ObjectPool;
using ObjectPoolSample;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
builder.Services.TryAddSingleton<ObjectPool<StringBuilder>>(serviceProvider =>
{
var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
var policy = new Microsoft.Extensions.ObjectPool.StringBuilderPooledObjectPolicy();
return provider.Create(policy);
});
builder.Services.AddWebEncoders();
var app = builder.Build();
// Test using /?firstname=Steve&lastName=Gordon&day=28&month=9
app.UseMiddleware<BirthdayMiddleware>();
app.MapGet("/", () => "Hello World!");
app.Run();
Следующий код реализует BirthdayMiddleware
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.Extensions.ObjectPool;
namespace ObjectPoolSample;
public class BirthdayMiddleware
{
private readonly RequestDelegate _next;
public BirthdayMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context,
ObjectPool<StringBuilder> builderPool)
{
if (context.Request.Query.TryGetValue("firstName", out var firstName) &&
context.Request.Query.TryGetValue("lastName", out var lastName) &&
context.Request.Query.TryGetValue("month", out var month) &&
context.Request.Query.TryGetValue("day", out var day) &&
int.TryParse(month, out var monthOfYear) &&
int.TryParse(day, out var dayOfMonth))
{
var now = DateTime.UtcNow; // Ignoring timezones.
// Request a StringBuilder from the pool.
var stringBuilder = builderPool.Get();
try
{
stringBuilder.Append("Hi ")
.Append(firstName).Append(" ").Append(lastName).Append(". ");
var encoder = context.RequestServices.GetRequiredService<HtmlEncoder>();
if (now.Day == dayOfMonth && now.Month == monthOfYear)
{
stringBuilder.Append("Happy birthday!!!");
var html = encoder.Encode(stringBuilder.ToString());
await context.Response.WriteAsync(html);
}
else
{
var thisYearsBirthday = new DateTime(now.Year, monthOfYear,
dayOfMonth);
int daysUntilBirthday = thisYearsBirthday > now
? (thisYearsBirthday - now).Days
: (thisYearsBirthday.AddYears(1) - now).Days;
stringBuilder.Append("There are ")
.Append(daysUntilBirthday).Append(" days until your birthday!");
var html = encoder.Encode(stringBuilder.ToString());
await context.Response.WriteAsync(html);
}
}
finally // Ensure this runs even if the main code throws.
{
// Return the StringBuilder to the pool.
builderPool.Return(stringBuilder);
}
return;
}
await _next(context);
}
}
Microsoft.Extensions.ObjectPool является частью инфраструктуры ASP.NET Core, которая поддерживает сохранение группы объектов в памяти для повторного использования, а не позволяет объектам собирать мусор.
Возможно, потребуется использовать пул объектов, если управляемые объекты:
- Затраты на выделение и инициализацию.
- Представляет ограниченный ресурс.
- Используется прогнозируемо и часто.
Например, платформа ASP.NET Core использует пул объектов в некоторых местах для повторного использования StringBuilder экземпляров. StringBuilder
выделяет и управляет собственными буферами для хранения символьных данных. ASP.NET Core регулярно используется StringBuilder
для реализации функций, а их повторное использование обеспечивает преимущество производительности.
Пул объектов не всегда повышает производительность:
- Если стоимость инициализации объекта высока, обычно это медленнее, чтобы получить объект из пула.
- Объекты, управляемые пулом, не выделяются без выделения пула.
Используйте пул объектов только после сбора данных о производительности с помощью реалистичных сценариев для приложения или библиотеки.
ПРЕДУПРЕЖДЕНИЕ. Не ObjectPool
реализуется IDisposable
. Мы не рекомендуем использовать его с типами, которые нуждаются в удалении.ObjectPool
в ASP.NET Core 3.0 и более поздних версий поддерживаетсяIDisposable
.
ПРИМЕЧАНИЕ. ObjectPool не помещает ограничение на количество выделенных объектов, оно помещает ограничение на количество объектов, которые он будет хранить.
Основные понятия
ObjectPool<T> — абстракция базового пула объектов. Используется для получения и возврата объектов.
PooledObjectPolicy<T> — реализуется для настройки создания объекта и способа сброса при возврате в пул. Это можно передать в пул объектов, который вы создаете напрямую.... ИЛИ
Create выступает в качестве фабрики для создания пулов объектов.
Объект ObjectPool можно использовать в приложении несколькими способами:
- Создание экземпляра пула.
- Регистрация пула в внедрении зависимостей (DI) в качестве экземпляра.
- Регистрация
ObjectPoolProvider<>
в di и его использование в качестве фабрики.
Использование ObjectPool
Вызов Get для получения объекта и Return возврата объекта. Нет необходимости возвращать каждый объект. Если объект не возвращается, он будет собирать мусор.
Пример ObjectPool
Следующий код:
- Добавляется
ObjectPoolProvider
в контейнер внедрения зависимостей (DI). - Добавляет и настраивает
ObjectPool<StringBuilder>
контейнер DI. BirthdayMiddleware
Добавляет объект .
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.TryAddSingleton<ObjectPool<StringBuilder>>(serviceProvider =>
{
var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
var policy = new StringBuilderPooledObjectPolicy();
return provider.Create(policy);
});
services.AddWebEncoders();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Test using /?firstname=Steve&lastName=Gordon&day=28&month=9
app.UseMiddleware<BirthdayMiddleware>();
}
}
Следующий код реализует BirthdayMiddleware
public class BirthdayMiddleware
{
private readonly RequestDelegate _next;
public BirthdayMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context,
ObjectPool<StringBuilder> builderPool)
{
if (context.Request.Query.TryGetValue("firstName", out var firstName) &&
context.Request.Query.TryGetValue("lastName", out var lastName) &&
context.Request.Query.TryGetValue("month", out var month) &&
context.Request.Query.TryGetValue("day", out var day) &&
int.TryParse(month, out var monthOfYear) &&
int.TryParse(day, out var dayOfMonth))
{
var now = DateTime.UtcNow; // Ignoring timezones.
// Request a StringBuilder from the pool.
var stringBuilder = builderPool.Get();
try
{
stringBuilder.Append("Hi ")
.Append(firstName).Append(" ").Append(lastName).Append(". ");
var encoder = context.RequestServices.GetRequiredService<HtmlEncoder>();
if (now.Day == dayOfMonth && now.Month == monthOfYear)
{
stringBuilder.Append("Happy birthday!!!");
var html = encoder.Encode(stringBuilder.ToString());
await context.Response.WriteAsync(html);
}
else
{
var thisYearsBirthday = new DateTime(now.Year, monthOfYear,
dayOfMonth);
int daysUntilBirthday = thisYearsBirthday > now
? (thisYearsBirthday - now).Days
: (thisYearsBirthday.AddYears(1) - now).Days;
stringBuilder.Append("There are ")
.Append(daysUntilBirthday).Append(" days until your birthday!");
var html = encoder.Encode(stringBuilder.ToString());
await context.Response.WriteAsync(html);
}
}
finally // Ensure this runs even if the main code throws.
{
// Return the StringBuilder to the pool.
builderPool.Return(stringBuilder);
}
return;
}
await _next(context);
}
}
ASP.NET Core