次の方法で共有


ASP.NET Core での ObjectPool によるオブジェクト再利用

作成者 : Günther FoidlSteve GordonSamson Amaugo

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 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 がスローされます。
  • Return を呼び出すと所与の項目が破棄されます。

重要な ObjectPool 型とインターフェイス:

  • ObjectPool<T> : 基本的なオブジェクト プール抽象化。 オブジェクトを取得して返すために使用します。
  • PooledObjectPolicy<T> : これを実装し、オブジェクトの作成方法と、プールに返されるときのリセット方法をカスタマイズします。 これは直接構築されるオブジェクト プールに渡すことができます。
  • IResettable : オブジェクト プールに戻ったときに、オブジェクトを自動的にリセットします。

ObjectPool は、次のような複数の方法でアプリで使用できます。

  • プールをインスタンス化する。
  • 依存関係の挿入 (DI) でプールをインスタンスとして登録する。
  • DI で ObjectPoolProvider<> を登録し、ファクトリとしてそれを使用する。

ObjectPool の使用方法

Get を呼び出してオブジェクトを取得し、Return を呼び出してオブジェクトを返します。 すべてのオブジェクトを返す必要はありません。 オブジェクトを返されない場合、ガベージ コレクションが実行されます。

ObjectPool サンプル

コード例を次に示します。

  • ObjectPoolProvider依存関係の挿入 (DI) コンテナーに追加します。
  • オブジェクト プールに返されたときにバッファーの内容を自動的にクリアする 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;
    }
}

メモ: プールされた型 TIResettable を実装していない場合は、カスタム 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 がスローされます。
  • Return を呼び出すと所与の項目が破棄されます。

重要な ObjectPool 型とインターフェイス:

  • ObjectPool<T> : 基本的なオブジェクト プール抽象化。 オブジェクトを取得して返すために使用します。
  • PooledObjectPolicy<T> : これを実装し、オブジェクトの作成方法と、プールに返されるときのリセット方法をカスタマイズします。 これは直接構築するオブジェクト プールに渡すことができます、もしくは
  • Create : オブジェクトを作成するためのファクトリとして機能します。
  • IResettable : オブジェクト プールに戻ったときに、オブジェクトを自動的にリセットします。

ObjectPool は、次のような複数の方法でアプリで使用できます。

  • プールをインスタンス化する。
  • 依存関係の挿入 (DI) でプールをインスタンスとして登録する。
  • DI で ObjectPoolProvider<> を登録し、ファクトリとしてそれを使用する。

ObjectPool の使用方法

Get を呼び出してオブジェクトを取得し、Return を呼び出してオブジェクトを返します。 すべてのオブジェクトを返す必要はありません。 オブジェクトを返さない場合、ガベージ コレクションが実行されます。

ObjectPool サンプル

コード例を次に示します。

  • ObjectPoolProvider依存関係の挿入 (DI) コンテナーに追加します。
  • 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 サンプル

コード例を次に示します。

  • ObjectPoolProvider依存関係の挿入 (DI) コンテナーに追加します。
  • 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);
    }
}