Поделиться через


Повторное использование объекта с ObjectPool в ASP.NET Core

Günther Foidl, Стив Гордон и Сэмсон Амуго

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

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);
    }
}