Sdílet prostřednictvím


Opakované použití objektů s objektovým fondem v ASP.NET Core

Günther Foidl, Steve Gordon a Samson Amaugo

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Microsoft.Extensions.ObjectPool je součástí ASP.NET základní infrastruktury, která podporuje zachování skupiny objektů v paměti, aby bylo možné znovu použít místo toho, aby objekty byly uvolněny z paměti. Všechny statické metody a metody instance jsou Microsoft.Extensions.ObjectPool bezpečné pro přístup z více vláken.

Aplikace můžou chtít fond objektů používat, pokud jsou spravované objekty:

  • Nákladné přidělení nebo inicializace
  • Představuje omezený prostředek.
  • Používá se předvídatelně a často.

Například architektura ASP.NET Core používá fond objektů na některých místech k opakovanému použití StringBuilder instancí. StringBuilder přiděluje a spravuje vlastní vyrovnávací paměti pro ukládání dat znaků. ASP.NET Core pravidelně používá StringBuilder k implementaci funkcí a jejich opakované použití poskytuje výhodu výkonu.

Sdružováníobjektůch

  • Pokud nejsou náklady na inicializaci objektu vysoké, je obvykle pomalejší získat objekt z fondu.
  • Objekty spravované fondem se nepřidělují, dokud se fond nepřidělí.

Sdružování objektů používejte pouze po shromáždění dat o výkonu pomocí realistických scénářů pro vaši aplikaci nebo knihovnu.

POZNÁMKA: ObjectPool neumisťuje limit počtu objektů, které přiděluje, umístí limit počtu objektů, které si zachová.

Koncepty fondu objektů

Při DefaultObjectPoolProvider použití a T implementaci IDisposable:

  • Položky, které se nevracejí do fondu, budou odstraněny.
  • Když se fond odstraní pomocí DI, všechny položky ve fondu se vyřadí.

POZNÁMKA: Po odstranění fondu:

  • Volání Get vyvolá chybu ObjectDisposedException.
  • Volání Return odstraní danou položku.

Důležité ObjectPool typy a rozhraní:

  • ObjectPool<T> : Abstrakce fondu základních objektů. Slouží k získání a vrácení objektů.
  • PooledObjectPolicy<T> : Implementujte to, abyste přizpůsobili způsob vytvoření objektu a jeho resetování při vrácení do fondu. Ten lze předat přímo do fondu objektů vytvořeného přímo.
  • IResettable : Automaticky resetuje objekt při vrácení do fondu objektů.

Fond objektů lze v aplikaci použít několika způsoby:

  • Vytvoření instance fondu
  • Registrace fondu v injektáži závislostí (DI) jako instance
  • ObjectPoolProvider<> Registrace v DI a jeho použití jako továrna

Jak používat ObjectPool

Volání Get pro získání objektu a Return vrácení objektu Není nutné vracet všechny objekty. Pokud se objekt nevrátí, bude uvolněný z paměti.

Ukázka objektového fondu

Následující kód:

  • Přidá ObjectPoolProvider do kontejneru injektáž závislostí (DI).
  • Implementuje rozhraní pro IResettable automatické vymazání obsahu vyrovnávací paměti při vrácení do fondu objektů.
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;
    }
}

POZNÁMKA: Pokud se typ T fondu neimplementuje IResettable, lze vlastní PooledObjectPolicy<T> použít k resetování stavu objektů předtím, než se vrátí do fondu.

Microsoft.Extensions.ObjectPool je součástí ASP.NET základní infrastruktury, která podporuje zachování skupiny objektů v paměti, aby bylo možné znovu použít místo toho, aby objekty byly uvolněny z paměti. Všechny statické metody a metody instance jsou Microsoft.Extensions.ObjectPool bezpečné pro přístup z více vláken.

Aplikace můžou chtít fond objektů používat, pokud jsou spravované objekty:

  • Nákladné přidělení nebo inicializace
  • Představuje omezený prostředek.
  • Používá se předvídatelně a často.

Například architektura ASP.NET Core používá fond objektů na některých místech k opakovanému použití StringBuilder instancí. StringBuilder přiděluje a spravuje vlastní vyrovnávací paměti pro ukládání dat znaků. ASP.NET Core pravidelně používá StringBuilder k implementaci funkcí a jejich opakované použití poskytuje výhodu výkonu.

Sdružováníobjektůch

  • Pokud nejsou náklady na inicializaci objektu vysoké, je obvykle pomalejší získat objekt z fondu.
  • Objekty spravované fondem se nepřidělují, dokud se fond nepřidělí.

Sdružování objektů používejte pouze po shromáždění dat o výkonu pomocí realistických scénářů pro vaši aplikaci nebo knihovnu.

POZNÁMKA: ObjectPool neumisťuje limit počtu objektů, které přiděluje, umístí limit počtu objektů, které si zachová.

Koncepty

Při DefaultObjectPoolProvider použití a T implementaci IDisposable:

  • Položky, které se nevracejí do fondu, budou odstraněny.
  • Když se fond odstraní pomocí DI, všechny položky ve fondu se vyřadí.

POZNÁMKA: Po odstranění fondu:

  • Volání Get vyvolá chybu ObjectDisposedException.
  • Volání Return odstraní danou položku.

Důležité ObjectPool typy a rozhraní:

  • ObjectPool<T> : Abstrakce fondu základních objektů. Slouží k získání a vrácení objektů.
  • PooledObjectPolicy<T> : Implementujte to, abyste přizpůsobili způsob vytvoření objektu a jeho resetování při vrácení do fondu. To lze předat do fondu objektů, který je konstruktor přímo, nebo
  • Create : Funguje jako továrna pro vytváření fondů objektů.
  • IResettable : Automaticky resetuje objekt při vrácení do fondu objektů.

Fond objektů lze v aplikaci použít několika způsoby:

  • Vytvoření instance fondu
  • Registrace fondu v injektáži závislostí (DI) jako instance
  • ObjectPoolProvider<> Registrace v DI a jeho použití jako továrna

Jak používat ObjectPool

Volání Get pro získání objektu a Return vrácení objektu Není nutné, abyste vraceli všechny objekty. Pokud objekt nevrátíte, bude uvolněný z paměti.

Ukázka objektového fondu

Následující kód:

  • Přidá ObjectPoolProvider do kontejneru injektáž závislostí (DI).
  • Přidá a nakonfiguruje ObjectPool<StringBuilder> kontejner DI.
  • Přidá .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();

Následující kód implementuje: 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 je součástí ASP.NET základní infrastruktury, která podporuje zachování skupiny objektů v paměti, aby bylo možné znovu použít místo toho, aby objekty byly uvolněny z paměti.

Fond objektů můžete chtít použít, pokud jsou spravované objekty:

  • Nákladné přidělení nebo inicializace
  • Představuje nějaký omezený prostředek.
  • Používá se předvídatelně a často.

Například architektura ASP.NET Core používá fond objektů na některých místech k opakovanému použití StringBuilder instancí. StringBuilder přiděluje a spravuje vlastní vyrovnávací paměti pro ukládání dat znaků. ASP.NET Core pravidelně používá StringBuilder k implementaci funkcí a jejich opakované použití poskytuje výhodu výkonu.

Sdružováníobjektůch

  • Pokud nejsou náklady na inicializaci objektu vysoké, je obvykle pomalejší získat objekt z fondu.
  • Objekty spravované fondem se nepřidělují, dokud se fond nepřidělí.

Sdružování objektů používejte pouze po shromáždění dat o výkonu pomocí realistických scénářů pro vaši aplikaci nebo knihovnu.

UPOZORNĚNÍ: Neimplementuje ObjectPoolIDisposable. Nedoporučujeme ho používat s typy, které potřebují k dispozici. ObjectPool v ASP.NET Core 3.0 a novější podporuje IDisposable.

POZNÁMKA: ObjectPool neumisťuje limit počtu objektů, které přidělí, umístí limit počtu objektů, které si zachová.

Koncepty

ObjectPool<T> - abstrakce základního fondu objektů. Slouží k získání a vrácení objektů.

PooledObjectPolicy<T> – Implementujte tuto možnost, abyste přizpůsobili způsob vytvoření objektu a jeho resetování při vrácení do fondu. Lze jej předat do fondu objektů, který jste vytvořili přímo.... NEBO

Create funguje jako továrna pro vytváření fondů objektů.

Fond objektů lze v aplikaci použít několika způsoby:

  • Vytvoření instance fondu
  • Registrace fondu v injektáži závislostí (DI) jako instance
  • ObjectPoolProvider<> Registrace v DI a jeho použití jako továrna

Jak používat ObjectPool

Volání Get pro získání objektu a Return vrácení objektu Není nutné, abyste vraceli všechny objekty. Pokud objekt nevrátíte, bude uvolněný z paměti.

Ukázka objektového fondu

Následující kód:

  • Přidá ObjectPoolProvider do kontejneru injektáž závislostí (DI).
  • Přidá a nakonfiguruje ObjectPool<StringBuilder> kontejner DI.
  • Přidá .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>(); 
    }
}

Následující kód implementuje: 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);
    }
}