Dela via


Återanvänd objekt med ObjectPool i ASP.NET Core

Av Günther Foidl, Steve Gordonoch Samson Amaugo

Anteckning

Det här är inte den senaste versionen av den här artikeln. För den aktuella utgåvan, se .NET 9-versionen av den här artikeln.

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den aktuella utgåvan, se .NET 9-versionen av den här artikeln.

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den aktuella utgåvan, se .NET 9-versionen av den här artikeln.

Microsoft.Extensions.ObjectPool är en del av ASP.NET Core-infrastrukturen som stödjer att hålla en grupp objekt i minnet för återanvändning i stället för att tillåta att objekten bli borttagna som skräp. Alla statiska metoder och instansmetoder i Microsoft.Extensions.ObjectPool är trådsäkra.

Appar kanske vill använda objektpoolen om de objekt som hanteras är:

  • Dyrt att allokera/initiera.
  • Representerar en begränsad resurs.
  • Används förutsägbart och ofta.

Till exempel använder ASP.NET Core-ramverket objektpoolen på vissa ställen för att återanvända StringBuilder instanser. StringBuilder allokerar och hanterar sina egna buffertar för att lagra teckendata. ASP.NET Core använder regelbundet StringBuilder för att implementera funktioner och att återanvända dem ger en prestandafördel.

Objektpooler förbättrar inte alltid prestanda:

  • Om inte initieringskostnaden för ett objekt är hög är det vanligtvis långsammare att hämta objektet från poolen.
  • Objekt som hanteras av poolen avallokeras inte förrän poolen har avallokerats.

Använd endast objektpooler när du har samlat in prestandadata med hjälp av realistiska scenarier för din app eller ditt bibliotek.

OBS! ObjectPool sätter ingen gräns för antalet objekt som den allokerar, den sätter en gräns för hur många objekt den behåller.

ObjectPool-begrepp

När DefaultObjectPoolProvider används och T implementerar IDisposable:

  • Objekt som inte returneras till poolen kommer att bortskaffas.
  • När poolen tas bort av DI tas alla objekt i poolen bort.

Obs! När poolen har kasserats:

  • Anropet till Get kastar ett ObjectDisposedException.
  • Anropar Return kastar bort det angivna objektet.

Viktiga ObjectPool typer och gränssnitt:

  • ObjectPool<T> : Den grundläggande objektpoolens abstraktion. Används för att hämta och returnera objekt.
  • PooledObjectPolicy<T> : Implementera detta för att anpassa hur ett objekt skapas och hur det återställs när det returneras till poolen. Detta kan skickas till en objektpool som konstrueras direkt.
  • IResettable : Återställer objektet automatiskt när det returneras till en objektpool.

ObjectPool kan användas i en app på flera sätt:

  • Instansiera en pool.
  • Registrera en pool i Beroendeinmatning (DI) som en instans.
  • Registrera ObjectPoolProvider<> i DI och använda den som en fabrik.

Så här använder du ObjectPool

Anropa Get för att hämta ett objekt och Return för att returnera objektet. Det finns inget krav på att returnera varje objekt. Om ett objekt inte returneras kommer det att skräpsamlas.

ObjectPool-exempel

Följande kod:

  • Lägger till ObjectPoolProvider i containern Dependency injection (DI).
  • Implementerar IResettable-gränssnittet för att automatiskt rensa innehållet i bufferten när det returneras till objektpoolen.
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;
    }
}

OBS! När pooltypen T inte implementerar IResettablekan en anpassad PooledObjectPolicy<T> användas för att återställa objektens tillstånd innan de returneras till poolen.

Microsoft.Extensions.ObjectPool är en del av ASP.NET Core-infrastrukturen som stödjer att hålla en grupp objekt i minnet för återanvändning i stället för att tillåta att objekten bli borttagna som skräp. Alla statiska metoder och instansmetoder i Microsoft.Extensions.ObjectPool är trådsäkra.

Appar kanske vill använda objektpoolen om de objekt som hanteras är:

  • Dyrt att allokera/initiera.
  • Representerar en begränsad resurs.
  • Används förutsägbart och ofta.

Till exempel använder ASP.NET Core-ramverket objektpoolen på vissa ställen för att återanvända StringBuilder instanser. StringBuilder allokerar och hanterar sina egna buffertar för att lagra teckendata. ASP.NET Core använder regelbundet StringBuilder för att implementera funktioner och att återanvända dem ger en prestandafördel.

Objektpooler förbättrar inte alltid prestanda:

  • Om inte initieringskostnaden för ett objekt är hög är det vanligtvis långsammare att hämta objektet från poolen.
  • Objekt som hanteras av poolen avallokeras inte förrän poolen har avallokerats.

Använd endast objektpooler när du har samlat in prestandadata med hjälp av realistiska scenarier för din app eller ditt bibliotek.

OBS! ObjectPool sätter ingen gräns för antalet objekt som den allokerar, den sätter en gräns för hur många objekt den behåller.

Begrepp

När DefaultObjectPoolProvider används och T implementerar IDisposable:

  • Objekt som inte returneras till poolen kommer att bortskaffas.
  • När poolen tas bort av DI tas alla objekt i poolen bort.

Obs! När poolen har kasserats:

  • Anropet till Get kastar ett ObjectDisposedException.
  • Anropar Return kastar bort det angivna objektet.

Viktiga ObjectPool typer och gränssnitt:

  • ObjectPool<T> : Den grundläggande objektpoolens abstraktion. Används för att hämta och returnera objekt.
  • PooledObjectPolicy<T> : Implementera detta för att anpassa hur ett objekt skapas och hur det återställs när det returneras till poolen. Detta kan skickas till en objektpool som är direkt konstruerad, eller
  • Create : Fungerar som en fabrik för att skapa objektpooler.
  • IResettable: Återställer objektet automatiskt när det returneras till en objektpool.

ObjectPool kan användas i en app på flera sätt:

  • Instansiera en pool.
  • Registrera en pool i Beroendeinmatning (DI) som en instans.
  • Registrera ObjectPoolProvider<> i DI och använda den som en fabrik.

Så här använder du ObjectPool

Anropa Get för att hämta ett objekt och Return för att returnera objektet. Det finns inget krav på att du returnerar varje objekt. Om du inte returnerar ett objekt kommer det att vara skräp som samlas in.

ObjectPool-exempel

Följande kod:

  • Lägger till ObjectPoolProvider i containern Dependency injection (DI).
  • Lägger till och konfigurerar ObjectPool<StringBuilder> till DI-containern.
  • Lägger till 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();

Följande kod implementerar 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 är en del av ASP.NET Core-infrastrukturen som stödjer att hålla en grupp objekt i minnet för återanvändning i stället för att tillåta att objekten bli borttagna som skräp.

Du kanske vill använda objektpoolen om de objekt som hanteras är:

  • Dyrt att allokera/initiera.
  • Representera en begränsad resurs.
  • Används förutsägbart och ofta.

Till exempel använder ASP.NET Core-ramverket objektpoolen på vissa ställen för att återanvända StringBuilder instanser. StringBuilder allokerar och hanterar sina egna buffertar för att lagra teckendata. ASP.NET Core använder regelbundet StringBuilder för att implementera funktioner och att återanvända dem ger en prestandafördel.

Objektpooler förbättrar inte alltid prestanda:

  • Om inte initieringskostnaden för ett objekt är hög är det vanligtvis långsammare att hämta objektet från poolen.
  • Objekt som hanteras av poolen avallokeras inte förrän poolen har avallokerats.

Använd endast objektpooler när du har samlat in prestandadata med hjälp av realistiska scenarier för din app eller ditt bibliotek.

VARNING! ObjectPool implementerar inte IDisposable. Vi rekommenderar inte att du använder den med typer som behöver bortskaffas. ObjectPool i ASP.NET Core 3.0 eller senare stöder IDisposable.

OBS! ObjectPool sätter ingen gräns för hur många objekt som ska allokeras. Den sätter en gräns för hur många objekt som ska behållas.

Begrepp

ObjectPool<T> – den grundläggande objektpoolens abstraktion. Används för att hämta och returnera objekt.

PooledObjectPolicy<T> – implementera detta för att anpassa hur ett objekt skapas och hur det återställs när det returneras till poolen. Detta kan skickas till en objektpool som du skapar direkt.... ELLER

Create fungerar som en fabrik för att skapa objektpooler.

ObjectPool kan användas i en app på flera sätt:

  • Instansiera en pool.
  • Registrera en pool i Beroendeinmatning (DI) som en instans.
  • Registrera ObjectPoolProvider<> i DI och använda den som en fabrik.

Så här använder du ObjectPool

Anropa Get för att hämta ett objekt och Return för att returnera objektet. Det finns inget krav på att du returnerar varje objekt. Om du inte returnerar ett objekt kommer det att vara skräp som samlas in.

ObjectPool-exempel

Följande kod:

  • Lägger till ObjectPoolProvider i containern Dependency injection (DI).
  • Lägger till och konfigurerar ObjectPool<StringBuilder> till DI-containern.
  • Lägger till 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>(); 
    }
}

Följande kod implementerar 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);
    }
}