在 ASP.NET Core 中使用 ObjectPool 重複使用物件
作者 :Günther Foidl、 Steve Gordon和 Samson Amaugo
注意
這不是本文的最新版本。 如需目前版本,請參閱 本文的 .NET 7 版本。
Microsoft.Extensions.ObjectPool是 ASP.NET Core基礎結構的一部分,可支援將一組物件保留在記憶體中以供重複使用,而不是允許垃圾收集物件。 中的所有 Microsoft.Extensions.ObjectPool
靜態和實例方法都是安全線程。
如果受管理的物件是,應用程式可能會想要使用物件集區:
- 配置/初始化的成本很高。
- 代表有限的資源。
- 可預測且經常使用。
例如,ASP.NET Core架構會在某些位置使用物件集區來重複使用 StringBuilder 實例。 StringBuilder
配置及管理自己的緩衝區,以保存字元資料。 ASP.NET Core定期使用 StringBuilder
來實作功能,並重複使用這些功能可提供效能優勢。
物件共用不一定會改善效能:
- 除非物件的初始化成本很高,否則從集區取得物件通常比較慢。
- 除非取消組態集區,否則不會取消組態集區所管理的物件。
只有在使用應用程式或程式庫的實際案例收集效能資料之後,才使用物件共用。
注意:ObjectPool 不會對它配置的物件數目設定限制,它會限制其保留的物件數目。
ObjectPool 概念
使用 並 T
實作 IDisposable
時 DefaultObjectPoolProvider :
- 不會傳回集區的專案將會處置。
- 當集區由 DI 處置時,會處置集區中的所有專案。
注意:處置集區之後:
- 呼叫
Get
會擲回ObjectDisposedException
。 - 呼叫
Return
會處置指定的專案。
重要 ObjectPool
類型和介面:
- ObjectPool<T> :基本物件集區抽象概念。 用來取得和傳回物件。
- PooledObjectPolicy<T> :實作此動作以自訂物件的建立方式,以及如何在傳回集區時重設物件。 這可以傳遞至直接建構的物件集區,或
- IResettable :當傳回物件集區時,自動重設物件。
ObjectPool 可以用多種方式在應用程式中使用:
- 具現化集區。
- 在相 依性插入 中註冊集區, (DI) 作為實例。
ObjectPoolProvider<>
在 DI 中註冊 ,並將其作為處理站使用。
如何使用 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 thit 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 不會對它配置的物件數目設定限制,它會限制其保留的物件數目。
概念
使用 並 T
實作 IDisposable
時 DefaultObjectPoolProvider :
- 不會傳回集區的專案將會處置。
- 當集區由 DI 處置時,會處置集區中的所有專案。
注意:處置集區之後:
- 呼叫
Get
會擲回ObjectDisposedException
。 - 呼叫
Return
會處置指定的專案。
重要 ObjectPool
類型和介面:
- ObjectPool<T> :基本物件集區抽象概念。 用來取得和傳回物件。
- PooledObjectPolicy<T> :實作此動作以自訂物件的建立方式,以及如何在傳回集區時重設物件。 這可以傳遞至直接建構的物件集區,或
- Create :做為建立物件集區的處理站。
- IResettable :在 傳回物件集區時自動重設物件。
ObjectPool 可以用多種方式在應用程式中使用:
- 具現化集區。
- 在相 依性插入 中註冊集區, (DI) 作為實例。
ObjectPoolProvider<>
在 DI 中註冊 ,並將其作為處理站使用。
如何使用 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
。 不建議將它與需要處置的類型搭配使用。 ObjectPool
在 ASP.NET Core 3.0 和更新版本中支援 IDisposable
。
注意:ObjectPool 不會對所配置的物件數目設定限制,它會限制將保留的物件數目。
概念
ObjectPool<T> - 基本物件集區抽象概念。 用來取得和傳回物件。
PooledObjectPolicy<T> - 實作此動作以自訂物件建立方式,以及如何在傳回集區時 重設 物件。 這可以傳遞至您直接建構的物件集區...或
Create 做為建立物件集區的處理站。
ObjectPool 可以用多種方式在應用程式中使用:
- 具現化集區。
- 在相 依性插入 中註冊集區, (DI) 作為實例。
ObjectPoolProvider<>
在 DI 中註冊 ,並將其作為處理站使用。
如何使用 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);
}
}