ObjectPool对象重用

使用 ASP.NET Core 中的 ObjectPool 对象重用

通过Steve GordonRyan Nowak,和Rick Anderson

Microsoft.Extensions.ObjectPool 是支持将一组对象保存在内存中以供重复使用,而不允许的对象进行垃圾收集的 ASP.NET Core 基础结构的一部分。

您可能想要使用对象池,如果正在管理的对象:

  • 成本分配/初始化。
  • 表示一些有限的资源。
  • 使用以可预测方式和频率。

例如,ASP.NET Core 框架将使用对象池在某些位置重复使用StringBuilder实例。 StringBuilder 分配和管理其自己的缓冲区来存放字符数据。 ASP.NET Core 定期使用StringBuilder为了实现功能,并重复使用它们提供性能优势。

对象池始终不会提高性能:

  • 对象的初始化成本较高,除非它是通常较慢,可从池中获取的对象。
  • 池管理的对象不解除分配,直到该池将被释放。

使用对象池仅在为应用或库使用实际的方案的性能数据收集后。

警告:ObjectPool不会实现IDisposable。我们不建议将用于需要可供使用的类型。

注意:ObjectPool 不置于将分配的对象数的限制,它将保留的对象数施加限制。

概念

ObjectPool<T> -基本对象池抽象。 用于获取和返回对象。

PooledObjectPolicy<T> -实现此接口可以自定义如何创建对象以及如何重置时返回到池中。 这可以传递到直接构造的对象池...或

Create 充当用于创建对象池的工厂。

ObjectPool 可以采用多种方式应用:

  • 实例化一个池。
  • 注册中的池依赖关系注入(DI) 为一个实例。
  • 注册ObjectPoolProvider<>在 DI 并将其用作工厂。

如何使用 ObjectPool

调用ObjectPool<T>来获取的对象和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);
        });
    }

    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);
    }
}