在 ASP.NET Core 中使用 ObjectPool 进行对象重用Object reuse with ObjectPool in ASP.NET Core

本文内容

作者: Steve GordonRyan NowakRick 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
  1. public class Startup
  2. {
  3. public void ConfigureServices(IServiceCollection services)
  4. {
  5. services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
  6. services.TryAddSingleton<ObjectPool<StringBuilder>>(serviceProvider =>
  7. {
  8. var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
  9. var policy = new StringBuilderPooledObjectPolicy();
  10. return provider.Create(policy);
  11. });
  12. }
  13. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  14. {
  15. if (env.IsDevelopment())
  16. {
  17. app.UseDeveloperExceptionPage();
  18. }
  19. // Test using /?firstname=Steve&lastName=Gordon&day=28&month=9
  20. app.UseMiddleware<BirthdayMiddleware>();
  21. }
  22. }

下面的代码实现 BirthdayMiddleware

  1. public class BirthdayMiddleware
  2. {
  3. private readonly RequestDelegate _next;
  4. public BirthdayMiddleware(RequestDelegate next)
  5. {
  6. _next = next;
  7. }
  8. public async Task InvokeAsync(HttpContext context,
  9. ObjectPool<StringBuilder> builderPool)
  10. {
  11. if (context.Request.Query.TryGetValue("firstName", out var firstName) &&
  12. context.Request.Query.TryGetValue("lastName", out var lastName) &&
  13. context.Request.Query.TryGetValue("month", out var month) &&
  14. context.Request.Query.TryGetValue("day", out var day) &&
  15. int.TryParse(month, out var monthOfYear) &&
  16. int.TryParse(day, out var dayOfMonth))
  17. {
  18. var now = DateTime.UtcNow; // Ignoring timezones.
  19. // Request a StringBuilder from the pool.
  20. var stringBuilder = builderPool.Get();
  21. try
  22. {
  23. stringBuilder.Append("Hi ")
  24. .Append(firstName).Append(" ").Append(lastName).Append(". ");
  25. if (now.Day == dayOfMonth && now.Month == monthOfYear)
  26. {
  27. stringBuilder.Append("Happy birthday!!!");
  28. await context.Response.WriteAsync(stringBuilder.ToString());
  29. }
  30. else
  31. {
  32. var thisYearsBirthday = new DateTime(now.Year, monthOfYear,
  33. dayOfMonth);
  34. int daysUntilBirthday = thisYearsBirthday > now
  35. ? (thisYearsBirthday - now).Days
  36. : (thisYearsBirthday.AddYears(1) - now).Days;
  37. stringBuilder.Append("There are ")
  38. .Append(daysUntilBirthday).Append(" days until your birthday!");
  39. await context.Response.WriteAsync(stringBuilder.ToString());
  40. }
  41. }
  42. finally // Ensure this runs even if the main code throws.
  43. {
  44. // Return the StringBuilder to the pool.
  45. builderPool.Return(stringBuilder);
  46. }
  47. return;
  48. }
  49. await _next(context);
  50. }
  51. }