ASP.NET Core での ObjectPool によるオブジェクト再利用
作成者 : Günther Foidl、 Steve Gordon、 Samson Amaugo
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
Microsoft.Extensions.ObjectPool は、オブジェクトのガベージ コレクションを許可するのではなく、オブジェクトのグループをメモリに保持して再利用できるようにする ASP.NET Core インフラストラクチャの一部です。 Microsoft.Extensions.ObjectPool
のすべての静的メソッドとインスタンス メソッド は、スレッド セーフです。
管理されているオブジェクトが次のような場合、オブジェクト プールを使用できます:
- 割り当て/初期化が高額になる。
- リソースが限られていることを表します。
- 予測的かつ頻繁に使用する
たとえば、ASP.NET Core フレームワークでは、StringBuilder インスタンスを再利用するために、いくつかの場所でオブジェクト プールを使用します。 StringBuilder
によって、独自のバッファーを割り当て、管理し、文字データを保持します。 ASP.NET Core では定期的に StringBuilder
を使用し、機能を実装します。それを再利用することでパフォーマンスの恩恵があります。
オブジェクト プーリングで常にパフォーマンスが向上するわけではありません。
- オブジェクトの初期化コストが高い場合を除き、通常、プールからオブジェクトを取得する方が遅くなります。
- プールによって管理されるオブジェクトは、プールが割り当て解除されるまで割り当て解除されません。
オブジェクト プーリングは、アプリまたはライブラリに現実的なシナリオを使用してパフォーマンス データを取得した後にのみ使用します。
注: ObjectPool では、割り当てるオブジェクト数に制限がありません。保持するオブジェクト数に制限があります。
ObjectPool の概念
DefaultObjectPoolProvider が使用され、T
で IDisposable
を実装する場合:
- プールに返されないアイテムは破棄されます。
- プールが 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;
}
}
メモ: プールされた型 T
が IResettable
を実装していない場合は、カスタム PooledObjectPolicy<T>
を使用して、オブジェクトがプールに返される前に状態をリセットできます。
Microsoft.Extensions.ObjectPool は、オブジェクトのガベージ コレクションを許可するのではなく、オブジェクトのグループをメモリに保持して再利用できるようにする ASP.NET Core インフラストラクチャの一部です。 Microsoft.Extensions.ObjectPool
のすべての静的メソッドとインスタンス メソッド は、スレッド セーフです。
管理されているオブジェクトが次のような場合、オブジェクト プールを使用できます:
- 割り当て/初期化が高額になる。
- リソースが限られていることを表します。
- 予測的かつ頻繁に使用する
たとえば、ASP.NET Core フレームワークでは、StringBuilder インスタンスを再利用するために、いくつかの場所でオブジェクト プールを使用します。 StringBuilder
によって、独自のバッファーを割り当て、管理し、文字データを保持します。 ASP.NET Core では定期的に StringBuilder
を使用し、機能を実装します。それを再利用することでパフォーマンスの恩恵があります。
オブジェクト プーリングで常にパフォーマンスが向上するわけではありません。
- オブジェクトの初期化コストが高い場合を除き、通常、プールからオブジェクトを取得する方が遅くなります。
- プールによって管理されるオブジェクトは、プールが割り当て解除されるまで割り当て解除されません。
オブジェクト プーリングは、アプリまたはライブラリに現実的なシナリオを使用してパフォーマンス データを取得した後にのみ使用します。
注: ObjectPool では、割り当てるオブジェクト数に制限がありません。保持するオブジェクト数に制限があります。
概念
DefaultObjectPoolProvider が使用され、T
で IDisposable
を実装する場合:
- プールに返されないアイテムは破棄されます。
- プールが 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
を使用し、機能を実装します。それを再利用することでパフォーマンスの恩恵があります。
オブジェクト プーリングで常にパフォーマンスが向上するわけではありません。
- オブジェクトの初期化コストが高い場合を除き、通常、プールからオブジェクトを取得する方が遅くなります。
- プールによって管理されるオブジェクトは、プールが割り当て解除されるまで割り当て解除されません。
オブジェクト プーリングは、アプリまたはライブラリに現実的なシナリオを使用してパフォーマンス データを取得した後にのみ使用します。
警告: ObjectPool
は IDisposable
を実装しません。 破棄を必要とする型でそれを使用することは推奨されません。 ASP.NET Core 3.0 以降の ObjectPool
で IDisposable
がサポートされます。
注: 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);
}
}
ASP.NET Core