ASP.NET Core'da ObjectPool ile nesne yeniden kullanımı

Tarafından Günther Foidl, Steve Gordon ve Samson Amaugo

Not

Bu, bu makalenin en son sürümü değildir. Geçerli sürüm için bu makalenin .NET 9 sürümüne bakın.

Uyarı

ASP.NET Core'un bu sürümü artık desteklenmiyor. Daha fazla bilgi için bkz . .NET ve .NET Core Destek İlkesi. Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.

Önemli

Bu bilgiler, ticari olarak piyasaya sürülmeden önce önemli ölçüde değiştirilebilen bir yayın öncesi ürünle ilgilidir. Burada verilen bilgilerle ilgili olarak Microsoft açık veya zımni hiçbir garanti vermez.

Geçerli sürüm için bu makalenin .NET 9 sürümüne bakın.

Microsoft.Extensions.ObjectPool , nesnelerin çöp toplanmasına izin vermek yerine bir grup nesnenin yeniden kullanılmak üzere bellekte tutulmasını destekleyen ASP.NET Core altyapısının bir parçasıdır. içindeki Microsoft.Extensions.ObjectPool tüm statik ve örnek yöntemleri iş parçacığı açısından güvenlidir.

Yönetilen nesneler şunlarsa uygulamalar nesne havuzunu kullanmak isteyebilir:

  • Ayırmak/başlatmak pahalıdır.
  • Sınırlı bir kaynağı temsil eder.
  • Tahmin edilebilir ve sık kullanılan.

Örneğin, ASP.NET Core çerçevesi örnekleri yeniden kullanmak StringBuilder için bazı yerlerde nesne havuzunu kullanır. StringBuilder karakter verilerini tutmak için kendi arabelleklerini ayırır ve yönetir. ASP.NET Core, özellikleri uygulamak için düzenli olarak kullanır StringBuilder ve bunları yeniden kullanmak bir performans avantajı sağlar.

Nesne havuzu her zaman performansı iyileştirmez:

  • Bir nesnenin başlatma maliyeti yüksek olmadığı sürece, nesneyi havuzdan almak genellikle daha yavaştır.
  • Havuz tarafından yönetilen nesneler, havuz kaldırılana kadar ayrılmaz.

Nesne havuzu oluşturmayı yalnızca uygulamanız veya kitaplığınız için gerçekçi senaryoları kullanarak performans verilerini topladıktan sonra kullanın.

NOT: ObjectPool, ayırdığı nesne sayısına bir sınır yerleştirmez, koruduğu nesne sayısına bir sınır yerleştirir.

ObjectPool kavramları

Ne zaman DefaultObjectPoolProvider kullanılır ve T uygulanır IDisposable:

  • Havuza döndürülmeyen öğeler atılır.
  • Havuz DI tarafından atıldığında havuzdaki tüm öğeler atılır.

NOT: Havuz atıldıktan sonra:

  • Çağrısı Get bir ObjectDisposedExceptionoluşturur.
  • Çağrısı Return , verilen öğeyi atılır.

Önemli ObjectPool türler ve arabirimler:

  • ObjectPool<T> : Temel nesne havuzu soyutlaması. Nesneleri almak ve döndürmek için kullanılır.
  • PooledObjectPolicy<T> : Bir nesnenin nasıl oluşturulduğunu ve havuza geri döndürülürken nasıl sıfırlanıp sıfırlanamını özelleştirmek için bunu uygulayın. Bu, doğrudan oluşturulacak bir nesne havuzuna geçirilebilir.
  • IResettable : Nesne havuzuna döndürülürken nesneyi otomatik olarak sıfırlar.

ObjectPool bir uygulamada birden çok şekilde kullanılabilir:

  • Havuz örneği oluşturma.
  • Bağımlılık ekleme (DI) içinde bir havuzu örnek olarak kaydetme.
  • 'yi ObjectPoolProvider<> DI'ye kaydetme ve fabrika olarak kullanma.

ObjectPool'u kullanma

Bir nesneyi almak ve Return nesneyi döndürmek için çağrısı Get yapın. Her nesneyi döndürme gereksinimi yoktur. Bir nesne döndürülmezse, çöp toplanır.

ObjectPool örneği

Aşağıdaki kod:

  • Bağımlılık ekleme (DI) kapsayıcısına eklerObjectPoolProvider.
  • Nesne havuzuna IResettable döndürülürken arabelleğin içeriğini otomatik olarak temizlemek için arabirimini uygular.
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;
    }
}

NOT: Havuza alınan tür T uygulamadığında IResettable, nesnelerin havuza döndürülmeden önce durumunu sıfırlamak için bir özel PooledObjectPolicy<T> kullanılabilir.

Microsoft.Extensions.ObjectPool , nesnelerin çöp toplanmasına izin vermek yerine bir grup nesnenin yeniden kullanılmak üzere bellekte tutulmasını destekleyen ASP.NET Core altyapısının bir parçasıdır. içindeki Microsoft.Extensions.ObjectPool tüm statik ve örnek yöntemleri iş parçacığı açısından güvenlidir.

Yönetilen nesneler şunlarsa uygulamalar nesne havuzunu kullanmak isteyebilir:

  • Ayırmak/başlatmak pahalıdır.
  • Sınırlı bir kaynağı temsil eder.
  • Tahmin edilebilir ve sık kullanılan.

Örneğin, ASP.NET Core çerçevesi örnekleri yeniden kullanmak StringBuilder için bazı yerlerde nesne havuzunu kullanır. StringBuilder karakter verilerini tutmak için kendi arabelleklerini ayırır ve yönetir. ASP.NET Core, özellikleri uygulamak için düzenli olarak kullanır StringBuilder ve bunları yeniden kullanmak bir performans avantajı sağlar.

Nesne havuzu her zaman performansı iyileştirmez:

  • Bir nesnenin başlatma maliyeti yüksek olmadığı sürece, nesneyi havuzdan almak genellikle daha yavaştır.
  • Havuz tarafından yönetilen nesneler, havuz kaldırılana kadar ayrılmaz.

Nesne havuzu oluşturmayı yalnızca uygulamanız veya kitaplığınız için gerçekçi senaryoları kullanarak performans verilerini topladıktan sonra kullanın.

NOT: ObjectPool, ayırdığı nesne sayısına bir sınır yerleştirmez, koruduğu nesne sayısına bir sınır yerleştirir.

Kavramlar

Ne zaman DefaultObjectPoolProvider kullanılır ve T uygulanır IDisposable:

  • Havuza döndürülmeyen öğeler atılır.
  • Havuz DI tarafından atıldığında havuzdaki tüm öğeler atılır.

NOT: Havuz atıldıktan sonra:

  • Çağrısı Get bir ObjectDisposedExceptionoluşturur.
  • Çağrısı Return , verilen öğeyi atılır.

Önemli ObjectPool türler ve arabirimler:

  • ObjectPool<T> : Temel nesne havuzu soyutlaması. Nesneleri almak ve döndürmek için kullanılır.
  • PooledObjectPolicy<T> : Bir nesnenin nasıl oluşturulduğunu ve havuza döndürülürken nasıl sıfırlanıp sıfırlanamını özelleştirmek için bunu uygulayın. Bu, doğrudan yapıya sahip bir nesne havuzuna geçirilebilir veya
  • Create : Nesne havuzları oluşturmak için fabrika görevi görür.
  • IResettable : Bir nesne havuzuna döndürildiğinde nesneyi otomatik olarak sıfırlar.

ObjectPool bir uygulamada birden çok şekilde kullanılabilir:

  • Havuz örneği oluşturma.
  • Bağımlılık ekleme (DI) içinde bir havuzu örnek olarak kaydetme.
  • 'yi ObjectPoolProvider<> DI'ye kaydetme ve fabrika olarak kullanma.

ObjectPool'u kullanma

Bir nesneyi almak ve Return nesneyi döndürmek için çağrısı Get yapın. Her nesneyi döndürmeniz gerekmez. Bir nesne döndürmezseniz, bu nesne çöp olarak toplanır.

ObjectPool örneği

Aşağıdaki kod:

  • Bağımlılık ekleme (DI) kapsayıcısına eklerObjectPoolProvider.
  • DI kapsayıcısına ekler ve yapılandırır ObjectPool<StringBuilder> .
  • öğesini BirthdayMiddlewareekler.
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();

Aşağıdaki kod şunu uygular: 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 , nesnelerin çöp toplanmasına izin vermek yerine bir grup nesnenin yeniden kullanılmak üzere bellekte tutulmasını destekleyen ASP.NET Core altyapısının bir parçasıdır.

Yönetilen nesneler şunlarsa nesne havuzunu kullanmak isteyebilirsiniz:

  • Ayırmak/başlatmak pahalıdır.
  • Sınırlı bir kaynağı temsil eder.
  • Tahmin edilebilir ve sık kullanılan.

Örneğin, ASP.NET Core çerçevesi örnekleri yeniden kullanmak StringBuilder için bazı yerlerde nesne havuzunu kullanır. StringBuilder karakter verilerini tutmak için kendi arabelleklerini ayırır ve yönetir. ASP.NET Core, özellikleri uygulamak için düzenli olarak kullanır StringBuilder ve bunları yeniden kullanmak bir performans avantajı sağlar.

Nesne havuzu her zaman performansı iyileştirmez:

  • Bir nesnenin başlatma maliyeti yüksek olmadığı sürece, nesneyi havuzdan almak genellikle daha yavaştır.
  • Havuz tarafından yönetilen nesneler, havuz kaldırılana kadar ayrılmaz.

Nesne havuzu oluşturmayı yalnızca uygulamanız veya kitaplığınız için gerçekçi senaryoları kullanarak performans verilerini topladıktan sonra kullanın.

UYARI: uygulamaz ObjectPool IDisposable. Elden çıkarılması gereken türlerde kullanılmasını önermeyiz.ObjectPool ASP.NET Core 3.0 ve üzeri sürümlerinde desteklenirIDisposable.

NOT: ObjectPool ayıracağı nesne sayısına bir sınır yerleştirmez, saklayacağı nesne sayısına bir sınır yerleştirir.

Kavramlar

ObjectPool<T> - temel nesne havuzu soyutlaması. Nesneleri almak ve döndürmek için kullanılır.

PooledObjectPolicy<T>- Bir nesnenin nasıl oluşturulduğunu ve havuza geri döndürülürken nasıl sıfırlanıp sıfırlanamını özelleştirmek için bunu uygulayın. Bu, doğrudan oluşturduğunuz bir nesne havuzuna geçirilebilir.... VEYA

Create nesne havuzları oluşturmak için bir fabrika işlevi görür.

ObjectPool bir uygulamada birden çok şekilde kullanılabilir:

  • Havuz örneği oluşturma.
  • Bağımlılık ekleme (DI) içinde bir havuzu örnek olarak kaydetme.
  • 'yi ObjectPoolProvider<> DI'ye kaydetme ve fabrika olarak kullanma.

ObjectPool'u kullanma

Bir nesneyi almak ve Return nesneyi döndürmek için çağrısı Get yapın. Her nesneyi döndürmeniz gerekmez. Bir nesne döndürmezseniz, bu nesne çöp olarak toplanır.

ObjectPool örneği

Aşağıdaki kod:

  • Bağımlılık ekleme (DI) kapsayıcısına eklerObjectPoolProvider.
  • DI kapsayıcısına ekler ve yapılandırır ObjectPool<StringBuilder> .
  • öğesini BirthdayMiddlewareekler.
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>(); 
    }
}

Aşağıdaki kod şunu uygular: 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);
    }
}