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. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Die aktuelle Version finden Sie in der .NET 9-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.
Die aktuelle Version finden Sie in der .NET 9-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 T
IDisposable
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 eineObjectDisposedException
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 T
IDisposable
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 eineObjectDisposedException
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);
}
}