Teilen über


Objektwiederverwendung mit ObjectPool in ASP.NET Core

Von Günther Foidl, Steve Gordon und Samson Amaugo

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Warnung

Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Microsoft.Extensions.ObjectPool ist Teil der ASP.NET Core-Infrastruktur, wodurch eine Gruppe von Objekten zur Wiederverwendung im Speicher gehalten werden kann, anstatt sie per Garbage Collection zu bereinigen. Alle statischen Methoden und Instanzmethoden in Microsoft.Extensions.ObjectPool sind threadsicher.

Für Apps kann die Verwendung des Objektpools erwogen werden, wenn auf die verwalteten Objekte Folgendes zutrifft:

  • Ihre Zuordnung/Initialisierung ist kostenintensiv.
  • Sie stellen eine begrenzte Ressource dar.
  • Sie werden vorhersagbar und häufig verwendet.

Das ASP.NET Core-Framework verwendet beispielsweise den Objektpool in manchen Bereichen zur Wiederverwendung von StringBuilder-Instanzen. StringBuilder weist eigene Puffer zu und verwaltet sie, um Zeichendaten zu speichern. ASP.NET Core nutzt regelmäßig StringBuilder, um Features zu implementieren, und deren Wiederverwendung bietet einen Leistungsvorteil.

Das Objektpooling verbessert nicht immer die Leistung:

  • Sofern die Initialisierungskosten eines Objekts nicht hoch sind, dauert es in der Regel länger, das Objekt aus dem Pool abzurufen.
  • Durch den Pool verwaltete Objekte werden erst wieder freigegeben, wenn der Pool freigegeben wird.

Setzen Sie das Objektpooling erst ein, nachdem Sie anhand realistischer Szenarien Leistungsdaten für Ihre App oder Bibliothek gesammelt haben.

HINWEIS: Der ObjectPool legt keinen Grenzwert für die Anzahl der von ihm zugewiesenen Objekte fest, sondern nur für die Anzahl der beibehaltenen Objekte.

ObjectPool-Konzepte

Wenn DefaultObjectPoolProvider verwendet wird und TIDisposable implementiert:

  • Objekte, die nicht an den Pool zurückgegeben werden, werden verworfen.
  • Wenn der Pool per Abhängigkeitsinjektion verworfen wird, werden alle Elemente im Pool verworfen.

HINWEIS: Nachdem der Pool verworfen wurde:

  • Der Aufruf von Get löst eine ObjectDisposedException aus.
  • Der Aufruf von Return verwirft das angegebene Element.

Wichtige ObjectPool-Typen und Schnittstellen:

  • ObjectPool<T>: Die grundlegende Abstraktion des Objektpools. Wird verwendet, um Objekte abzurufen und zurückzugeben.
  • PooledObjectPolicy<T>: Durch die Implementierung dieses Typs können Sie festlegen, wie ein Objekt erstellt und wie es zurückgesetzt wird, wenn es an den Pool zurückgegeben wird. Dies kann an einen Objektpool übergeben werden, der direkt konstruiert wird.
  • IResettable: Setzt das Objekt automatisch zurück, wenn es an einen Objektpool zurückgegeben wird.

Der ObjectPool kann in einer App auf verschiedene Weise verwendet werden:

  • Instanziieren eines Pools
  • Registrieren eines Pools bei der Abhängigkeitsinjektion als Instanz
  • Registrieren von ObjectPoolProvider<> bei der Abhängigkeitsinjektion und Verwendung als Factory

Verwenden von ObjectPool

Rufen Sie Get auf, um ein Objekt zu erhalten und Return, um das Objekt zurückzugeben. Es ist nicht erforderlich, jedes Objekt zurückzugeben. Wird ein Objekt nicht zurückgegeben, wird es per Garbage Collection bereinigt.

ObjectPool-Beispiel

Der folgende Code führt folgende Aktionen aus:

  • Fügt ObjectPoolProvider dem Container für die Abhängigkeitsinjektion hinzu.
  • Implementiert die IResettable-Schnittstelle, um den Inhalt des Puffers bei der Rückgabe an den Objektpool automatisch zu löschen.
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;
    }
}

HINWEIS: Wenn der gepoolte Typ T nicht IResettable implementiert, kann der Zustand der Objekte mit einer benutzerdefinierten PooledObjectPolicy<T> zurückgesetzt werden, bevor sie an den Pool zurückgegeben werden.

Microsoft.Extensions.ObjectPool ist Teil der ASP.NET Core-Infrastruktur, wodurch eine Gruppe von Objekten zur Wiederverwendung im Speicher gehalten werden kann, anstatt sie per Garbage Collection zu bereinigen. Alle statischen Methoden und Instanzmethoden in Microsoft.Extensions.ObjectPool sind threadsicher.

Für Apps kann die Verwendung des Objektpools erwogen werden, wenn auf die verwalteten Objekte Folgendes zutrifft:

  • Ihre Zuordnung/Initialisierung ist kostenintensiv.
  • Sie stellen eine begrenzte Ressource dar.
  • Sie werden vorhersagbar und häufig verwendet.

Das ASP.NET Core-Framework verwendet beispielsweise den Objektpool in manchen Bereichen zur Wiederverwendung von StringBuilder-Instanzen. StringBuilder weist eigene Puffer zu und verwaltet sie, um Zeichendaten zu speichern. ASP.NET Core nutzt regelmäßig StringBuilder, um Features zu implementieren, und deren Wiederverwendung bietet einen Leistungsvorteil.

Das Objektpooling verbessert nicht immer die Leistung:

  • Sofern die Initialisierungskosten eines Objekts nicht hoch sind, dauert es in der Regel länger, das Objekt aus dem Pool abzurufen.
  • Durch den Pool verwaltete Objekte werden erst wieder freigegeben, wenn der Pool freigegeben wird.

Setzen Sie das Objektpooling erst ein, nachdem Sie anhand realistischer Szenarien Leistungsdaten für Ihre App oder Bibliothek gesammelt haben.

HINWEIS: Der ObjectPool legt keinen Grenzwert für die Anzahl der von ihm zugewiesenen Objekte fest, sondern nur für die Anzahl der beibehaltenen Objekte.

Konzepte

Wenn DefaultObjectPoolProvider verwendet wird und TIDisposable implementiert:

  • Objekte, die nicht an den Pool zurückgegeben werden, werden verworfen.
  • Wenn der Pool per Abhängigkeitsinjektion verworfen wird, werden alle Elemente im Pool verworfen.

HINWEIS: Nachdem der Pool verworfen wurde:

  • Der Aufruf von Get löst eine ObjectDisposedException aus.
  • Der Aufruf von Return verwirft das angegebene Element.

Wichtige ObjectPool-Typen und Schnittstellen:

  • ObjectPool<T>: Die grundlegende Abstraktion des Objektpools. Wird verwendet, um Objekte abzurufen und zurückzugeben.
  • PooledObjectPolicy<T>: Durch die Implementierung dieses Typs können Sie festlegen, wie ein Objekt erstellt und wie es zurückgesetzt wird, wenn es an den Pool zurückgegeben wird. Eine Übergabe kann an einen direkt konstruierten Objektpool erfolgen, oder
  • Create: Dient als Factory zum Erstellen von Objektpools.
  • IResettable: Setzt das Objekt automatisch zurück, wenn es an einen Objektpool zurückgegeben wird.

Der ObjectPool kann in einer App auf verschiedene Weise verwendet werden:

  • Instanziieren eines Pools
  • Registrieren eines Pools bei der Abhängigkeitsinjektion als Instanz
  • Registrieren von ObjectPoolProvider<> bei der Abhängigkeitsinjektion und Verwendung als Factory

Verwenden von ObjectPool

Rufen Sie Get auf, um ein Objekt zu erhalten und Return, um das Objekt zurückzugeben. Es ist nicht erforderlich, jedes Objekt zurückzugeben. Wenn Sie ein Objekt nicht zurückgeben, wird es per Garbage Collection bereinigt.

ObjectPool-Beispiel

Der folgende Code führt folgende Aktionen aus:

  • Fügt ObjectPoolProvider dem Container für die Abhängigkeitsinjektion hinzu.
  • Fügt ObjectPool<StringBuilder> dem Container für die Abhängigkeitsinjektion hinzu und konfiguriert ihn.
  • Fügt die BirthdayMiddleware hinzu.
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();

Der folgende Code implementiert 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 ist Teil der ASP.NET Core-Infrastruktur, wodurch eine Gruppe von Objekten zur Wiederverwendung im Speicher gehalten werden kann, anstatt sie per Garbage Collection zu bereinigen.

Sie können die Verwendung des Objektpools erwägen, wenn auf die verwalteten Objekte Folgendes zutrifft:

  • Ihre Zuordnung/Initialisierung ist kostenintensiv.
  • Sie stellen eine begrenzte Ressource dar.
  • Sie werden vorhersagbar und häufig verwendet.

Das ASP.NET Core-Framework verwendet beispielsweise den Objektpool in manchen Bereichen zur Wiederverwendung von StringBuilder-Instanzen. StringBuilder weist eigene Puffer zu und verwaltet sie, um Zeichendaten zu speichern. ASP.NET Core nutzt regelmäßig StringBuilder, um Features zu implementieren, und deren Wiederverwendung bietet einen Leistungsvorteil.

Das Objektpooling verbessert nicht immer die Leistung:

  • Sofern die Initialisierungskosten eines Objekts nicht hoch sind, dauert es in der Regel länger, das Objekt aus dem Pool abzurufen.
  • Durch den Pool verwaltete Objekte werden erst wieder freigegeben, wenn der Pool freigegeben wird.

Setzen Sie das Objektpooling erst ein, nachdem Sie anhand realistischer Szenarien Leistungsdaten für Ihre App oder Bibliothek gesammelt haben.

WARNUNG: IDisposable wird vom ObjectPool nicht implementiert. Wir raten von der Verwendung mit Typen ab, die verworfen werden müssen. ObjectPool in ASP.NET Core 3.0 und höher bietet Unterstützung für IDisposable.

HINWEIS: Der ObjectPool legt keinen Grenzwert für die Anzahl der von ihm zugewiesenen Objekte fest, sondern nur für die Anzahl der beibehaltenen Objekte.

Konzepte

ObjectPool<T>: Die grundlegende Abstraktion des Objektpools. Wird verwendet, um Objekte abzurufen und zurückzugeben.

PooledObjectPolicy<T>: Durch die Implementierung dieses Typs können Sie festlegen, wie ein Objekt erstellt und wie es zurückgesetzt wird, wenn es an den Pool zurückgegeben wird. Eine Übergabe kann an einen direkt konstruierten Objektpool erfolgen, oder

Create: Dient als Factory zum Erstellen von Objektpools.

Der ObjectPool kann in einer App auf verschiedene Weise verwendet werden:

  • Instanziieren eines Pools
  • Registrieren eines Pools bei der Abhängigkeitsinjektion als Instanz
  • Registrieren von ObjectPoolProvider<> bei der Abhängigkeitsinjektion und Verwendung als Factory

Verwenden von ObjectPool

Rufen Sie Get auf, um ein Objekt zu erhalten und Return, um das Objekt zurückzugeben. Es ist nicht erforderlich, jedes Objekt zurückzugeben. Wenn Sie ein Objekt nicht zurückgeben, wird es per Garbage Collection bereinigt.

ObjectPool-Beispiel

Der folgende Code führt folgende Aktionen aus:

  • Fügt ObjectPoolProvider dem Container für die Abhängigkeitsinjektion hinzu.
  • Fügt ObjectPool<StringBuilder> dem Container für die Abhängigkeitsinjektion hinzu und konfiguriert ihn.
  • Fügt die BirthdayMiddleware hinzu.
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>(); 
    }
}

Der folgende Code implementiert 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);
    }
}