Reutilização de objeto com ObjectPool no ASP.NET Core
Por Günther Foidl, Steve Gordon e Samson Amaugo
Observação
Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Microsoft.Extensions.ObjectPool faz parte da infraestrutura do ASP.NET Core que dá suporte à manutenção de um grupo de objetos na memória para reutilização, em vez de permitir que os objetos sejam coletados como lixo. Todos os métodos estáticos e de instância em Microsoft.Extensions.ObjectPool
são thread-safe.
Talvez os aplicativos queiram usar o pool de objetos se os objetos que estão sendo gerenciados forem:
- Caro para alocar/inicializar.
- Representa um recurso limitado.
- Usado previsivelmente e com frequência.
Por exemplo, a estrutura ASP.NET Core usa o pool de objetos em alguns locais para reutilizar instâncias do StringBuilder. StringBuilder
aloca e gerencia seus próprios buffers para armazenar dados de caracteres. O ASP.NET Core usa StringBuilder
regularmente para implementar recursos e reutilizá-los oferece um benefício de desempenho.
O pool de objetos nem sempre melhora o desempenho:
- A menos que o custo de inicialização de um objeto seja alto, geralmente é mais lento obter o objeto do pool.
- Os objetos gerenciados pelo pool não são desalocados até que o pool seja desalocado.
Use o pool de objetos somente depois de coletar dados de desempenho usando cenários realistas para seu aplicativo ou biblioteca.
OBSERVAÇÃO: o ObjectPool não coloca um limite no número de objetos alocados, coloca um limite no número de objetos que ele retém.
Conceitos de ObjectPool
Quando DefaultObjectPoolProvider é usado e T
implementa IDisposable
:
- Os itens que não forem retornados ao pool serão descartados.
- Quando o pool é descartado pela DI, todos os itens no pool são descartados.
OBSERVAÇÃO: depois que o pool é descartado:
- Chamar
Get
gera umObjectDisposedException
. - Chamar
Return
descarta o item especificado.
Tipos e interfaces importantes do ObjectPool
:
- ObjectPool<T>: a abstração básica do pool de objetos. Usado para obter e retornar objetos.
- PooledObjectPolicy<T>: implemente isso para personalizar como um objeto é criado e como ele é redefinido quando retornado ao pool. Isso pode ser passado para um pool de objetos que é construído diretamente.
- IResettable: redefine automaticamente o objeto quando retornado para um pool de objetos.
O ObjectPool pode ser usado em um aplicativo de várias maneiras:
- Instanciando um pool.
- Registrar um pool na Injeção de dependência(DI) como uma instância.
- Registrando o
ObjectPoolProvider<>
na DI e usando-o como uma fábrica.
Como usar ObjectPool
Chame Get para obter um objeto e Return para retornar o objeto . Não há nenhum requisito para retornar todos os objetos. Se um objeto não for retornado, ele será coletado como lixo.
Exemplo de ObjectPool
O seguinte código:
- Adiciona
ObjectPoolProvider
ao contêiner de Injeção de dependência(DI). - Implementa a interface do
IResettable
para limpar automaticamente o conteúdo do buffer quando retornado ao pool 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;
}
}
OBSERVAÇÃO: Quando o tipo T
em pool não implementa IResettable
, um PooledObjectPolicy<T>
personalizado pode ser usado para redefinir o estado dos objetos antes que eles sejam retornados ao pool.
Microsoft.Extensions.ObjectPool faz parte da infraestrutura do ASP.NET Core que dá suporte à manutenção de um grupo de objetos na memória para reutilização, em vez de permitir que os objetos sejam coletados como lixo. Todos os métodos estáticos e de instância em Microsoft.Extensions.ObjectPool
são thread-safe.
Talvez os aplicativos queiram usar o pool de objetos se os objetos que estão sendo gerenciados forem:
- Caro para alocar/inicializar.
- Representa um recurso limitado.
- Usado previsivelmente e com frequência.
Por exemplo, a estrutura ASP.NET Core usa o pool de objetos em alguns locais para reutilizar instâncias do StringBuilder. StringBuilder
aloca e gerencia seus próprios buffers para armazenar dados de caracteres. O ASP.NET Core usa StringBuilder
regularmente para implementar recursos e reutilizá-los oferece um benefício de desempenho.
O pool de objetos nem sempre melhora o desempenho:
- A menos que o custo de inicialização de um objeto seja alto, geralmente é mais lento obter o objeto do pool.
- Os objetos gerenciados pelo pool não são desalocados até que o pool seja desalocado.
Use o pool de objetos somente depois de coletar dados de desempenho usando cenários realistas para seu aplicativo ou biblioteca.
OBSERVAÇÃO: o ObjectPool não coloca um limite no número de objetos alocados, coloca um limite no número de objetos que ele retém.
Conceitos
Quando DefaultObjectPoolProvider é usado e T
implementa IDisposable
:
- Os itens que não forem retornados ao pool serão descartados.
- Quando o pool é descartado pela DI, todos os itens no pool são descartados.
OBSERVAÇÃO: depois que o pool é descartado:
- Chamar
Get
gera umObjectDisposedException
. - Chamar
Return
descarta o item especificado.
Tipos e interfaces importantes do ObjectPool
:
- ObjectPool<T>: a abstração básica do pool de objetos. Usado para obter e retornar objetos.
- PooledObjectPolicy<T>: implemente isso para personalizar como um objeto é criado e como ele é redefinido quando retornado ao pool. Isso pode ser passado para um pool de objetos que é construído diretamente ou
- Create: atua como uma fábrica para criar pools de objetos.
- IResettable: redefine automaticamente o objeto quando retornado para um pool de objetos.
O ObjectPool pode ser usado em um aplicativo de várias maneiras:
- Instanciando um pool.
- Registrar um pool na Injeção de dependência(DI) como uma instância.
- Registrando o
ObjectPoolProvider<>
na DI e usando-o como uma fábrica.
Como usar ObjectPool
Chame Get para obter um objeto e Return para retornar o objeto . Não há nenhum requisito de que você retorne todos os objetos. Se você não retornar um objeto, ele será coletado.
Exemplo de ObjectPool
O seguinte código:
- Adiciona
ObjectPoolProvider
ao contêiner de Injeção de dependência(DI). - Adiciona e configura
ObjectPool<StringBuilder>
ao contêiner de DI. - Adiciona o
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();
O código a seguir 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 faz parte da infraestrutura do ASP.NET Core que dá suporte à manutenção de um grupo de objetos na memória para reutilização, em vez de permitir que os objetos sejam coletados como lixo.
Talvez você queira usar o pool de objetos se os objetos que estão sendo gerenciados forem:
- Caro para alocar/inicializar.
- Representa algum recurso limitado.
- Usado previsivelmente e com frequência.
Por exemplo, a estrutura ASP.NET Core usa o pool de objetos em alguns locais para reutilizar instâncias do StringBuilder. StringBuilder
aloca e gerencia seus próprios buffers para armazenar dados de caracteres. O ASP.NET Core usa StringBuilder
regularmente para implementar recursos e reutilizá-los oferece um benefício de desempenho.
O pool de objetos nem sempre melhora o desempenho:
- A menos que o custo de inicialização de um objeto seja alto, geralmente é mais lento obter o objeto do pool.
- Os objetos gerenciados pelo pool não são desalocados até que o pool seja desalocado.
Use o pool de objetos somente depois de coletar dados de desempenho usando cenários realistas para seu aplicativo ou biblioteca.
AVISO: o ObjectPool
não implementa IDisposable
. Não recomendamos usá-lo com tipos que precisam de descarte. O ObjectPool
no ASP.NET Core 3.0 e posteriores é compatível comIDisposable
.
OBSERVAÇÃO: o ObjectPool não coloca um limite no número de objetos alocados, coloca um limite no número de objetos que ele retém.
Conceitos
ObjectPool<T>: a abstração básica do pool de objetos. Usado para obter e retornar objetos.
PooledObjectPolicy<T>: implemente isso para personalizar como um objeto é criado e como ele é redefinido quando retornado ao pool. Isso pode ser passado para um pool de objetos que você constrói diretamente...... OU
Create: atua como uma fábrica para criar pools de objetos.
O ObjectPool pode ser usado em um aplicativo de várias maneiras:
- Instanciando um pool.
- Registrar um pool na Injeção de dependência(DI) como uma instância.
- Registrando o
ObjectPoolProvider<>
na DI e usando-o como uma fábrica.
Como usar ObjectPool
Chame Get para obter um objeto e Return para retornar o objeto . Não há nenhum requisito de que você retorne todos os objetos. Se você não retornar um objeto, ele será coletado.
Exemplo de ObjectPool
O seguinte código:
- Adiciona
ObjectPoolProvider
ao contêiner de Injeção de dependência(DI). - Adiciona e configura
ObjectPool<StringBuilder>
ao contêiner de DI. - Adiciona o
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>();
}
}
O código a seguir 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);
}
}