在 ASP.NET Core 中使用 ObjectPool 进行对象重用Object reuse with ObjectPool in ASP.NET Core
本文内容
作者: Steve Gordon、 Ryan Nowak和Rick Anderson
Microsoft.Extensions.ObjectPool 是 ASP.NET Core 基础结构的一部分,该基础结构支持在内存中保留一组对象以供重复使用,而不是允许对对象进行垃圾回收。
如果要管理的对象是,则你可能需要使用对象池:
- 分配/初始化成本高昂。
- 表示某些有限资源。
- 使用可预测和频繁。
例如,ASP.NET Core 框架在某些位置使用对象池来重复使用 StringBuilder 实例。StringBuilder
分配并管理自己的用于保存字符数据的缓冲区。ASP.NET Core 会定期使用 StringBuilder
来实现功能,并重复使用这些功能以提高性能。
对象池并不总是能提高性能:
- 除非对象的初始化开销较高,否则从池中获取该对象的速度通常较慢。
- 在取消分配池之前,不会释放池管理的对象。
仅在使用应用或库的现实方案收集性能数据后,才使用对象池。
警告: ObjectPool
未实现 IDisposable
。建议不要将其与需要处置的类型一起使用。
注意: ObjectPool 不会对它将分配的对象数量施加限制,它会限制将保留的对象数。
概念Concepts
ObjectPool<T>-基本对象池抽象。用于获取和返回对象。
PooledObjectPolicy<T> 实现此方法,以便自定义对象的创建方式,以及如何在返回到池时重置对象。这可以传递到你直接构造的对象池中 .。。或
Create 充当创建对象池的工厂。
可以通过多种方式在应用中使用 ObjectPool:
- 实例化池。
- 在依赖关系注入(DI)中将池注册为实例。
- 在 DI 中注册
ObjectPoolProvider<>
,并将其作为工厂使用。
如何使用 ObjectPoolHow to use ObjectPool
调用 ObjectPool<T> 以获取对象并 Return 返回对象。不要求你返回每个对象。如果不返回对象,将对其进行垃圾回收。
ObjectPool 示例ObjectPool sample
以下代码:
- 将
ObjectPoolProvider
添加到依赖关系注入(DI)容器中。 - 向 DI 容器添加并配置
ObjectPool<StringBuilder>
。 - 添加
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);
});
}
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(". ");
if (now.Day == dayOfMonth && now.Month == monthOfYear)
{
stringBuilder.Append("Happy birthday!!!");
await context.Response.WriteAsync(stringBuilder.ToString());
}
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!");
await context.Response.WriteAsync(stringBuilder.ToString());
}
}
finally // Ensure this runs even if the main code throws.
{
// Return the StringBuilder to the pool.
builderPool.Return(stringBuilder);
}
return;
}
await _next(context);
}
}