Compartir a través de


Reutilización de objetos con ObjectPool en ASP.NET Core

De Günther Foidl, Steve Gordon y Samson Amaugo

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión de .NET 9 de este artículo.

Microsoft.Extensions.ObjectPool forma parte de la infraestructura de ASP.NET Core que admite mantener un grupo de objetos en memoria para su reutilización en lugar de permitir que los objetos se recopilen como elementos no utilizados. Todos los métodos estáticos y de instancia de Microsoft.Extensions.ObjectPool son seguros para subprocesos.

Es posible que las aplicaciones quieran usar el grupo de objetos si los objetos que se administran son:

  • Costosos de asignar o inicializar.
  • Representan un recurso limitado.
  • Se usan de forma predecible y frecuente.

Por ejemplo, el marco de ASP.NET Core usa el grupo de objetos en algunos lugares para reutilizar instancias StringBuilder. StringBuilder asigna y administra sus propios búferes para contener datos de caracteres. ASP.NET Core usa StringBuilder periódicamente para implementar características y reutilizarlas proporciona una ventaja de rendimiento.

La agrupación de objetos no siempre mejora el rendimiento:

  • A menos que el costo de inicialización de un objeto sea alto, normalmente es más lento obtener el objeto del grupo.
  • Los objetos administrados por el grupo no se desasignarán hasta que se desasigne el grupo.

Use la agrupación de objetos solo después de recopilar datos de rendimiento mediante escenarios realistas para la aplicación o biblioteca.

NOTA: ObjectPool no coloca un límite en el número de objetos que asigna, coloca un límite en el número de objetos que conserva.

Conceptos de ObjectPool

Cuando DefaultObjectPoolProvider se usa y T implementa IDisposable:

  • Se eliminarán los elementos que no se devuelven al grupo.
  • Cuando el grupo se elimina mediante inserción de dependencias, se eliminan todos los elementos del grupo.

NOTA: Después de eliminar el grupo:

  • La llamada Get produce un ObjectDisposedException.
  • La llamada Return elimina el elemento especificado.

Tipos e interfaces importantes de ObjectPool:

  • ObjectPool<T> : abstracción básica del grupo de objetos. Se usa para obtener y devolver objetos.
  • PooledObjectPolicy<T> : implemente esto para personalizar cómo se crea un objeto y cómo se restablece cuando se devuelve al grupo. Esto se puede pasar a un grupo de objetos construido directamente.
  • IResettable : restablece automáticamente el objeto cuando se devuelve a un grupo de objetos.

ObjectPool se puede usar en una aplicación de varias maneras:

  • Creación de instancias de un grupo.
  • Registro de un grupo en inserción de dependencias (DI) como instancia.
  • Registro en la inserción de dependencias de ObjectPoolProvider<> y usarlo como fábrica.

Cómo usar ObjectPool

Llame a Get para obtener un objeto y a Return para devolver el objeto. No es necesario devolver todos los objetos. Si no se devuelve un objeto, se recopilará como elemento no utilizado.

Muestra de ObjectPool

El código siguiente:

  • Agrega ObjectPoolProvider al contenedor de inserción de dependencias (DI).
  • Implementa la interfaz IResettable para borrar automáticamente el contenido del búfer cuando se devuelve al grupo de objetos.
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;
    }
}

NOTA: Cuando el tipo agrupado T no implementa IResettable, se puede usar un PooledObjectPolicy<T> personalizado para restablecer el estado de los objetos antes de que se devuelvan al grupo.

Microsoft.Extensions.ObjectPool forma parte de la infraestructura de ASP.NET Core que admite mantener un grupo de objetos en memoria para su reutilización en lugar de permitir que los objetos se recopilen como elementos no utilizados. Todos los métodos estáticos y de instancia de Microsoft.Extensions.ObjectPool son seguros para subprocesos.

Es posible que las aplicaciones quieran usar el grupo de objetos si los objetos que se administran son:

  • Costosos de asignar o inicializar.
  • Representan un recurso limitado.
  • Se usan de forma predecible y frecuente.

Por ejemplo, el marco de ASP.NET Core usa el grupo de objetos en algunos lugares para reutilizar instancias StringBuilder. StringBuilder asigna y administra sus propios búferes para contener datos de caracteres. ASP.NET Core usa StringBuilder periódicamente para implementar características y reutilizarlas proporciona una ventaja de rendimiento.

La agrupación de objetos no siempre mejora el rendimiento:

  • A menos que el costo de inicialización de un objeto sea alto, normalmente es más lento obtener el objeto del grupo.
  • Los objetos administrados por el grupo no se desasignarán hasta que se desasigne el grupo.

Use la agrupación de objetos solo después de recopilar datos de rendimiento mediante escenarios realistas para la aplicación o biblioteca.

NOTA: ObjectPool no coloca un límite en el número de objetos que asigna, coloca un límite en el número de objetos que conserva.

Conceptos

Cuando DefaultObjectPoolProvider se usa y T implementa IDisposable:

  • Se eliminarán los elementos que no se devuelven al grupo.
  • Cuando el grupo se elimina mediante inserción de dependencias, se eliminan todos los elementos del grupo.

NOTA: Después de eliminar el grupo:

  • La llamada Get produce un ObjectDisposedException.
  • La llamada Return elimina el elemento especificado.

Tipos e interfaces importantes de ObjectPool:

  • ObjectPool<T> : abstracción básica del grupo de objetos. Se usa para obtener y devolver objetos.
  • PooledObjectPolicy<T> : implemente esto para personalizar cómo se crea un objeto y cómo se restablece cuando se devuelve al grupo. Esto se puede pasar a un grupo de objetos que se construye directamente o
  • Create : actúa como fábrica para crear grupos de objetos.
  • IResettable : restablece automáticamente el objeto cuando se devuelve a un grupo de objetos.

ObjectPool se puede usar en una aplicación de varias maneras:

  • Creación de instancias de un grupo.
  • Registro de un grupo en inserción de dependencias (DI) como instancia.
  • Registro en la inserción de dependencias de ObjectPoolProvider<> y usarlo como fábrica.

Cómo usar ObjectPool

Llame a Get para obtener un objeto y a Return para devolver el objeto. No es necesario que devuelva todos los objetos. Si no devuelve un objeto, se recopilará como elemento no utilizado.

Muestra de ObjectPool

El código siguiente:

  • Agrega ObjectPoolProvider al contenedor de inserción de dependencias (DI).
  • Agrega y configura ObjectPool<StringBuilder> al contenedor de inserción de dependencias.
  • Agrega 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();

El código siguiente implementa 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 forma parte de la infraestructura de ASP.NET Core que admite mantener un grupo de objetos en memoria para su reutilización en lugar de permitir que los objetos se recopilen como elementos no utilizados.

Es posible quiera usar el grupo de objetos si los objetos que se administran son:

  • Costosos de asignar o inicializar.
  • Representan algún recurso limitado.
  • Se usan de forma predecible y frecuente.

Por ejemplo, el marco de ASP.NET Core usa el grupo de objetos en algunos lugares para reutilizar instancias StringBuilder. StringBuilder asigna y administra sus propios búferes para contener datos de caracteres. ASP.NET Core usa StringBuilder periódicamente para implementar características y reutilizarlas proporciona una ventaja de rendimiento.

La agrupación de objetos no siempre mejora el rendimiento:

  • A menos que el costo de inicialización de un objeto sea alto, normalmente es más lento obtener el objeto del grupo.
  • Los objetos administrados por el grupo no se desasignarán hasta que se desasigne el grupo.

Use la agrupación de objetos solo después de recopilar datos de rendimiento mediante escenarios realistas para la aplicación o biblioteca.

ADVERTENCIA: ObjectPool no implementa IDisposable. No se recomienda usarlo con tipos que necesiten eliminación. ObjectPool en ASP.NET 3.0 Core y versiones posteriores admite IDisposable.

NOTA: ObjectPool no coloca un límite en el número de objetos que asignará, coloca un límite en el número de objetos que conservará.

Conceptos

ObjectPool<T> : abstracción básica del grupo de objetos. Se usa para obtener y devolver objetos.

PooledObjectPolicy<T> : implemente esto para personalizar cómo se crea un objeto y cómo se restablece cuando se devuelve al grupo. Esto se puede pasar a un grupo de objetos que construye directamente O

Create actúa como fábrica para crear grupos de objetos.

ObjectPool se puede usar en una aplicación de varias maneras:

  • Creación de instancias de un grupo.
  • Registro de un grupo en inserción de dependencias (DI) como instancia.
  • Registro en la inserción de dependencias de ObjectPoolProvider<> y usarlo como fábrica.

Cómo usar ObjectPool

Llame a Get para obtener un objeto y a Return para devolver el objeto. No es necesario que devuelva todos los objetos. Si no devuelve un objeto, se recopilará como elemento no utilizado.

Muestra de ObjectPool

El código siguiente:

  • Agrega ObjectPoolProvider al contenedor de inserción de dependencias (DI).
  • Agrega y configura ObjectPool<StringBuilder> al contenedor de inserción de dependencias.
  • Agrega 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>(); 
    }
}

El código siguiente implementa 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);
    }
}