Share via


ASP.NET Core에서 ObjectPool로 개체 다시 사용

귄터 포이들, 스티브 고든, 삼손 아마우고

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Microsoft.Extensions.ObjectPool은 개체의 가비지 수집을 허용하는 대신 다시 사용할 수 있도록 메모리에 개체 그룹을 유지하도록 지원하는 ASP.NET Core 인프라의 일부입니다. Microsoft.Extensions.ObjectPool의 모든 정적 및 인스턴스 메서드는 스레드로부터 안전합니다.

관리형 개체가 다음과 같은 경우 앱에서 개체 풀을 사용할 수 있습니다.

  • 할당/초기화 비용이 많이 듭니다.
  • 제한된 리소스를 나타냅니다.
  • 예측 가능하고 자주 사용됩니다.

예를 들어 ASP.NET Core 프레임워크는 일부 위치에 있는 개체 풀을 사용하여 StringBuilder 인스턴스를 다시 사용합니다. StringBuilder는 문자 데이터를 보관할 자체 버퍼를 할당하고 관리합니다. ASP.NET Core는 정기적으로 StringBuilder를 사용하여 기능을 구현하고, 재사용으로 성능 이점을 제공합니다.

개체 풀링이 항상 성능을 향상시키는 것은 아닙니다.

  • 개체의 초기화 비용이 높지 않으면 일반적으로 풀에서 개체를 얻는 것이 더 느립니다.
  • 풀에서 관리하는 개체는 풀이 할당 해제될 때까지 할당이 해제되지 않습니다.

앱 또는 라이브러리에 대한 현실적인 시나리오를 사용하여 성능 데이터를 수집한 후에만 개체 풀링을 사용합니다.

참고: ObjectPool은 할당 개체 수에 제한을 두지 않고 보존 개체 수에 제한을 둡니다.

ObjectPool 개념

DefaultObjectPoolProvider가 사용되고 TIDisposable을 구현하는 경우:

  • 풀에 반환되지 않는 항목은 삭제됩니다.
  • DI에서 풀이 삭제되면 풀의 모든 항목이 삭제됩니다.

참고: 풀이 삭제된 후:

  • Get을 호출하면 ObjectDisposedException이 throw됩니다.
  • Return을 호출하면 지정된 항목이 삭제됩니다.

중요한 ObjectPool 형식 및 인터페이스:

  • ObjectPool<T> : 기본 개체 풀 추상화입니다. 개체를 얻고 반환하는 데 사용됩니다.
  • PooledObjectPolicy<T> : 개체를 만드는 방법과 풀로 반환할 때 개체를 다시 설정하는 방법을 사용자 지정하도록 구현합니다. 직접 생성된 개체 풀에 전달할 수 있습니다.
  • IResettable : 개체 풀로 반환되면 개체를 자동으로 다시 설정합니다.

ObjectPool은 다음과 같은 여러 가지 방법으로 앱에서 사용할 수 있습니다.

  • 풀을 인스턴스화합니다.
  • DI(종속성 주입)에서 풀을 인스턴스로 등록합니다.
  • DI에 ObjectPoolProvider<>를 등록하고 팩터리로 사용합니다.

ObjectPool을 사용하는 방법

Get을 호출하여 개체를 얻고 Return을 호출하여 개체를 반환합니다. 모든 개체를 반환할 필요가 없습니다. 개체가 반환되지 않으면 가비지 수집됩니다.

ObjectPool 샘플

코드는 다음과 같습니다.

  • ObjectPoolProviderDI(종속성 주입) 컨테이너에 추가합니다.
  • 개체 풀로 IResettable 반환되는 경우 버퍼의 내용을 자동으로 지우는 인터페이스를 구현합니다.
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;
    }
}

참고: 풀된 형식 T 이 구현 IResettable되지 않는 경우 풀로 반환되기 전에 사용자 지정 PooledObjectPolicy<T> 을 사용하여 개체의 상태를 다시 설정할 수 있습니다.

Microsoft.Extensions.ObjectPool은 개체의 가비지 수집을 허용하는 대신 다시 사용할 수 있도록 메모리에 개체 그룹을 유지하도록 지원하는 ASP.NET Core 인프라의 일부입니다. Microsoft.Extensions.ObjectPool의 모든 정적 및 인스턴스 메서드는 스레드로부터 안전합니다.

관리형 개체가 다음과 같은 경우 앱에서 개체 풀을 사용할 수 있습니다.

  • 할당/초기화 비용이 많이 듭니다.
  • 제한된 리소스를 나타냅니다.
  • 예측 가능하고 자주 사용됩니다.

예를 들어 ASP.NET Core 프레임워크는 일부 위치에 있는 개체 풀을 사용하여 StringBuilder 인스턴스를 다시 사용합니다. StringBuilder는 문자 데이터를 보관할 자체 버퍼를 할당하고 관리합니다. ASP.NET Core는 정기적으로 StringBuilder를 사용하여 기능을 구현하고, 재사용으로 성능 이점을 제공합니다.

개체 풀링이 항상 성능을 향상시키는 것은 아닙니다.

  • 개체의 초기화 비용이 높지 않으면 일반적으로 풀에서 개체를 얻는 것이 더 느립니다.
  • 풀에서 관리하는 개체는 풀이 할당 해제될 때까지 할당이 해제되지 않습니다.

앱 또는 라이브러리에 대한 현실적인 시나리오를 사용하여 성능 데이터를 수집한 후에만 개체 풀링을 사용합니다.

참고: ObjectPool은 할당 개체 수에 제한을 두지 않고 보존 개체 수에 제한을 둡니다.

개념

DefaultObjectPoolProvider가 사용되고 TIDisposable을 구현하는 경우:

  • 풀에 반환되지 않는 항목은 삭제됩니다.
  • DI에서 풀이 삭제되면 풀의 모든 항목이 삭제됩니다.

참고: 풀이 삭제된 후:

  • Get을 호출하면 ObjectDisposedException이 throw됩니다.
  • Return을 호출하면 지정된 항목이 삭제됩니다.

중요한 ObjectPool 형식 및 인터페이스:

  • ObjectPool<T> : 기본 개체 풀 추상화입니다. 개체를 얻고 반환하는 데 사용됩니다.
  • PooledObjectPolicy<T> : 개체를 만드는 방법과 풀로 반환할 때 개체를 다시 설정하는 방법을 사용자 지정하도록 구현합니다. 직접 생성되는 개체 풀에 전달하거나
  • Create : 개체 풀을 만들기 위한 팩터리 역할을 합니다.
  • IResettable : 개체 풀로 반환되면 개체를 자동으로 다시 설정합니다.

ObjectPool은 다음과 같은 여러 가지 방법으로 앱에서 사용할 수 있습니다.

  • 풀을 인스턴스화합니다.
  • DI(종속성 주입)에서 풀을 인스턴스로 등록합니다.
  • DI에 ObjectPoolProvider<>를 등록하고 팩터리로 사용합니다.

ObjectPool을 사용하는 방법

Get을 호출하여 개체를 얻고 Return을 호출하여 개체를 반환합니다. 모든 개체를 반환할 필요는 없습니다. 개체를 반환하지 않으면 가비지 수집이 수행됩니다.

ObjectPool 샘플

코드는 다음과 같습니다.

  • ObjectPoolProviderDI(종속성 주입) 컨테이너에 추가합니다.
  • ObjectPool<StringBuilder>를 DI 컨테이너에 추가하고 구성합니다.
  • 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();

다음 코드는 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은 개체의 가비지 수집을 허용하는 대신 다시 사용할 수 있도록 메모리에 개체 그룹을 유지하도록 지원하는 ASP.NET Core 인프라의 일부입니다.

관리되는 개체가 다음과 같은 경우 개체 풀을 사용할 수 있습니다.

  • 할당/초기화 비용이 많이 듭니다.
  • 제한된 리소스를 나타냅니다.
  • 예측 가능하고 자주 사용됩니다.

예를 들어 ASP.NET Core 프레임워크는 일부 위치에 있는 개체 풀을 사용하여 StringBuilder 인스턴스를 다시 사용합니다. StringBuilder는 문자 데이터를 보관할 자체 버퍼를 할당하고 관리합니다. ASP.NET Core는 정기적으로 StringBuilder를 사용하여 기능을 구현하고, 재사용으로 성능 이점을 제공합니다.

개체 풀링이 항상 성능을 향상시키는 것은 아닙니다.

  • 개체의 초기화 비용이 높지 않으면 일반적으로 풀에서 개체를 얻는 것이 더 느립니다.
  • 풀에서 관리하는 개체는 풀이 할당 해제될 때까지 할당이 해제되지 않습니다.

앱 또는 라이브러리에 대한 현실적인 시나리오를 사용하여 성능 데이터를 수집한 후에만 개체 풀링을 사용합니다.

경고: ObjectPoolIDisposable을 구현하지 않습니다. 삭제가 필요한 형식에는 사용하지 않는 것이 좋습니다. ASP.NET Core 3.0 이상의 ObjectPoolIDisposable을 지원합니다.

참고: ObjectPool은 할당할 개체 수에 제한을 두지 않고 보존할 개체 수에 제한을 둡니다.

개념

ObjectPool<T> - 기본 개체 풀 추상화입니다. 개체를 얻고 반환하는 데 사용됩니다.

PooledObjectPolicy<T> - 개체를 만드는 방법 및 풀에 반환할 때 개체를 초기화하는 방법을 사용자 지정하기 위해 이를 구현합니다. 직접 생성한 개체 풀에 전달할 수 있습니다. 또는

Create는 개체 풀을 만들기 위한 팩터리 역할을 합니다.

ObjectPool은 다음과 같은 여러 가지 방법으로 앱에서 사용할 수 있습니다.

  • 풀을 인스턴스화합니다.
  • DI(종속성 주입)에서 풀을 인스턴스로 등록합니다.
  • DI에 ObjectPoolProvider<>를 등록하고 팩터리로 사용합니다.

ObjectPool을 사용하는 방법

Get을 호출하여 개체를 얻고 Return을 호출하여 개체를 반환합니다. 모든 개체를 반환할 필요는 없습니다. 개체를 반환하지 않으면 가비지 수집이 수행됩니다.

ObjectPool 샘플

코드는 다음과 같습니다.

  • ObjectPoolProviderDI(종속성 주입) 컨테이너에 추가합니다.
  • ObjectPool<StringBuilder>를 DI 컨테이너에 추가하고 구성합니다.
  • 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>(); 
    }
}

다음 코드는 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);
    }
}