Share via


Réutilisation d’objets avec ObjectPool dans ASP.NET Core

Par Günther Foidl, Steve Gordon et Samson Amaugo

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.

Important

Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.

Pour la version actuelle, consultez la version .NET 8 de cet article.

Microsoft.Extensions.ObjectPool fait partie de l’infrastructure ASP.NET Core qui prend en charge la conservation d’un groupe d’objets en mémoire à des fins de réutilisation au lieu de permettre le nettoyage de la mémoire des objets. Toutes les méthodes statiques et instance dans Microsoft.Extensions.ObjectPool sont thread-safe.

Les applications peuvent souhaiter utiliser le pool d’objets si les objets gérés rencontrent les situations suivantes :

  • L’allocation/l’initialisation est coûteuse.
  • Les objets gérés représentent une ressource limitée.
  • Ils sont utilisés de manière prévisible et fréquemment.

Par exemple, l’infrastructure ASP.NET Core utilise le pool d’objets à certains endroits pour réutiliser les instances StringBuilder. StringBuilder alloue et gère ses propres mémoires tampons pour contenir les données caractères. ASP.NET Core utilise régulièrement StringBuilder pour implémenter des fonctionnalités, et les réutiliser offre un avantage en matière de performances.

La mise en pool d’objets n’améliore pas toujours les performances :

  • Sauf si le coût d’initialisation d’un objet est élevé, il est généralement plus lent d’obtenir l’objet à partir du pool.
  • Les objets gérés par le pool ne sont pas désalloués tant que le pool n’est pas désalloué.

Utilisez le regroupement d’objets uniquement après avoir collecté des données de performances à l’aide de scénarios réalistes pour votre application ou bibliothèque.

REMARQUE : Le pool d’objets ne limite pas le nombre d’objets qu’il alloue, il place une limite sur le nombre d’objets qu’il conserve.

Concepts d’ObjectPool

Quand DefaultObjectPoolProvider est utilisé et T implémente IDisposable :

  • Les éléments qui ne sont pas retournés au pool seront supprimés.
  • Lorsque le pool est supprimé par DI, tous les éléments du pool sont supprimés.

REMARQUE : Une fois le pool supprimé :

  • L’appel Get lève un ObjectDisposedException.
  • L’appel Return supprime l’élément donné.

Types et interfaces ObjectPool importants :

  • ObjectPool<T> : abstraction du pool d’objets de base. Utilisé pour obtenir et retourner des objets.
  • PooledObjectPolicy<T> : implémentez ceci pour personnaliser la façon dont un objet est créé et comment il est réinitialisé quand il est retourné au pool. Ceci peut être passé dans un pool d’objets qui est construit directement.
  • IResettable : réinitialise automatiquement l’objet lorsqu’il est retourné à un pool d’objets.

Le pool d’objets peut être utilisé de plusieurs façons dans une application :

  • Instanciation d’un pool.
  • Inscription d’un pool dans l’injection de dépendances (DI) en tant qu’instance.
  • Inscription du ObjectPoolProvider<> dans DI et utilisation de celui-ci en tant que fabrique.

Comment utiliser ObjectPool

Appelez Get pour obtenir un objet et Return pour retourner l’objet. Il n’est pas nécessaire de retourner chaque objet. Si aucun objet n’est retourné, il est récupéré par la mémoire.

Exemple ObjectPool

Le code suivant :

  • Ajoute ObjectPoolProvider au conteneur d’injection de dépendances (DI).
  • Implémente l’interface IResettable pour effacer automatiquement le contenu de la mémoire tampon lorsqu’elle est retournée au pool d’objets.
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;
    }
}

NOTE : Lorsque le type T mis en pool n’implémente pas IResettable, un PooledObjectPolicy<T> personnalisé peut être utilisé pour réinitialiser l’état des objets avant qu’ils ne soient retournés au pool.

Microsoft.Extensions.ObjectPool fait partie de l’infrastructure ASP.NET Core qui prend en charge la conservation d’un groupe d’objets en mémoire à des fins de réutilisation au lieu de permettre le nettoyage de la mémoire des objets. Toutes les méthodes statiques et instance dans Microsoft.Extensions.ObjectPool sont thread-safe.

Les applications peuvent souhaiter utiliser le pool d’objets si les objets gérés rencontrent les situations suivantes :

  • L’allocation/l’initialisation est coûteuse.
  • Les objets gérés représentent une ressource limitée.
  • Ils sont utilisés de manière prévisible et fréquemment.

Par exemple, l’infrastructure ASP.NET Core utilise le pool d’objets à certains endroits pour réutiliser les instances StringBuilder. StringBuilder alloue et gère ses propres mémoires tampons pour contenir les données caractères. ASP.NET Core utilise régulièrement StringBuilder pour implémenter des fonctionnalités, et les réutiliser offre un avantage en matière de performances.

La mise en pool d’objets n’améliore pas toujours les performances :

  • Sauf si le coût d’initialisation d’un objet est élevé, il est généralement plus lent d’obtenir l’objet à partir du pool.
  • Les objets gérés par le pool ne sont pas désalloués tant que le pool n’est pas désalloué.

Utilisez le regroupement d’objets uniquement après avoir collecté des données de performances à l’aide de scénarios réalistes pour votre application ou bibliothèque.

REMARQUE : Le pool d’objets ne limite pas le nombre d’objets qu’il alloue, il place une limite sur le nombre d’objets qu’il conserve.

Concepts

Quand DefaultObjectPoolProvider est utilisé et T implémente IDisposable :

  • Les éléments qui ne sont pas retournés au pool seront supprimés.
  • Lorsque le pool est supprimé par DI, tous les éléments du pool sont supprimés.

REMARQUE : Une fois le pool supprimé :

  • L’appel Get lève un ObjectDisposedException.
  • L’appel Return supprime l’élément donné.

Types et interfaces ObjectPool importants :

  • ObjectPool<T> : abstraction du pool d’objets de base. Utilisé pour obtenir et retourner des objets.
  • PooledObjectPolicy<T> : implémentez ceci pour personnaliser la façon dont un objet est créé et comment il est réinitialisé lorsqu’il est retourné au pool. Cela peut être passé dans un pool d’objets qui est construit directement, ou
  • Create : agit comme une fabrique pour la création de pools d’objets.
  • IResettable : réinitialise automatiquement l’objet lorsqu’il est retourné à un pool d’objets.

Le pool d’objets peut être utilisé de plusieurs façons dans une application :

  • Instanciation d’un pool.
  • Inscription d’un pool dans l’injection de dépendances (DI) en tant qu’instance.
  • Inscription du ObjectPoolProvider<> dans DI et utilisation de celui-ci en tant que fabrique.

Comment utiliser ObjectPool

Appelez Get pour obtenir un objet et Return pour retourner l’objet. Il n’est pas nécessaire que vous renvoyiez chaque objet. Si vous ne retournez pas d’objet, il sera récupéré par la mémoire.

Exemple ObjectPool

Le code suivant :

  • Ajoute ObjectPoolProvider au conteneur d’injection de dépendances (DI).
  • Ajoute et configure ObjectPool<StringBuilder> au conteneur d’adresses DI.
  • Ajoute le 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();

Le code suivant implémente 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 fait partie de l’infrastructure ASP.NET Core qui prend en charge la conservation d’un groupe d’objets en mémoire à des fins de réutilisation au lieu de permettre le nettoyage de la mémoire des objets.

Vous pouvez utiliser le pool d’objets si les objets gérés rencontrent les situations suivantes :

  • L’allocation/l’initialisation est coûteuse.
  • Les objets gérés représentent une ressource limitée.
  • Ils sont utilisés de manière prévisible et fréquemment.

Par exemple, l’infrastructure ASP.NET Core utilise le pool d’objets à certains endroits pour réutiliser les instances StringBuilder. StringBuilder alloue et gère ses propres mémoires tampons pour contenir les données caractères. ASP.NET Core utilise régulièrement StringBuilder pour implémenter des fonctionnalités, et les réutiliser offre un avantage en matière de performances.

La mise en pool d’objets n’améliore pas toujours les performances :

  • Sauf si le coût d’initialisation d’un objet est élevé, il est généralement plus lent d’obtenir l’objet à partir du pool.
  • Les objets gérés par le pool ne sont pas désalloués tant que le pool n’est pas désalloué.

Utilisez le regroupement d’objets uniquement après avoir collecté des données de performances à l’aide de scénarios réalistes pour votre application ou bibliothèque.

AVERTISSEMENT : ObjectPool n’implémente pas IDisposable. Nous vous déconseillons de l’utiliser avec les types qui doivent être retirés. ObjectPoolIDisposable est pris en charge dans ASP.NET Core 3.0 et versions ultérieures.

REMARQUE : Le pool d’objets ne limite pas le nombre d’objets qu’il alloue, il place une limite sur le nombre d’objets qu’il conserve.

Concepts

ObjectPool<T> : abstraction du pool d’objets de base. Utilisé pour obtenir et retourner des objets.

PooledObjectPolicy<T> : implémentez ceci pour personnaliser la façon dont un objet est créé et comment il est réinitialisé lorsqu’il est retourné au pool. Cela peut être passé dans un pool d’objets que vous construisez directement... OU

Create agit comme une fabrique pour la création de pools d’objets.

Le pool d’objets peut être utilisé de plusieurs façons dans une application :

  • Instanciation d’un pool.
  • Inscription d’un pool dans l’injection de dépendances (DI) en tant qu’instance.
  • Inscription du ObjectPoolProvider<> dans DI et utilisation de celui-ci en tant que fabrique.

Comment utiliser ObjectPool

Appelez Get pour obtenir un objet et Return pour retourner l’objet. Il n’est pas nécessaire que vous renvoyiez chaque objet. Si vous ne retournez pas d’objet, il sera récupéré par la mémoire.

Exemple ObjectPool

Le code suivant :

  • Ajoute ObjectPoolProvider au conteneur d’injection de dépendances (DI).
  • Ajoute et configure ObjectPool<StringBuilder> au conteneur d’adresses DI.
  • Ajoute le 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>(); 
    }
}

Le code suivant implémente 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);
    }
}