环境说明:
- .NET 8.0
- Microsoft.Extensions.DependencyInjection v9.0.0
- Microsoft.Extensions.ObjectPool v9.0.0
ObjectPool重用对象
先看微软官方文档的描述:
Microsoft.Extensions.ObjectPool它支持将一组对象保留在内存中以供重用,而不是允许对对象进行垃圾回收
如果要管理的对象具有以下特征,应用可能希望使用对象池:
- 分配/初始化成本高昂
- 表示有限资源
- 可预见地频繁使用
比如使用对象池来重用StringBuilder实例。 StringBuilder分配并管理自己的缓冲区来保存字符数据。如果经常使用StringBuilder来实现功能,重用这些对象会带来性能优势
对象池并不总是能提高性能:
- 除非对象的初始化成本很高,否则从池中获取对象通常较慢
- 在池解除分配之前,池管理的对象无法解除分配
- 仅在使用应用或库的真实场景收集性能数据后才使用对象池
注意:ObjectPool不限制分配的对象数量,但限制保留的对象数量
使用ObjectPool
调用Get获取对象,调用Return返回对象。 不必返回每个对象。 如果某个对象未返回,系统将对其进行垃圾回收
使用示例
这个示例展示了如何在控制台应用程序中使用对象池来重用大型缓冲区,避免重复分配内存,提高性能。每次计算哈希值时都会重用同一个缓冲区对象,而不是创建新的
要点:
- 将
ObjectPoolProvider
添加到依赖项注入 (DI) 容器 - 实现
IResettable
接口,以在返回到对象池时自动清除缓冲区的内容
IResettable接口的作用是当对象被返回到对象池时:
- 对象池会自动调用TryReset()方法
- 可以自定义清除缓冲区中的敏感数据
- 为下次使用做准备
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System.Security.Cryptography;class Program
{public class ReusableBuffer : IResettable{private readonly string _id = Guid.NewGuid().ToString("N").Substring(0, 6);private int _useCount = 0;public byte[] Data { get; } = new byte[1024 * 1024]; // 1 MBpublic ReusableBuffer(){Console.WriteLine($"创建新的缓冲区 ID: {_id}");}public bool TryReset(){_useCount++;Console.WriteLine($"重置缓冲区 ID: {_id}, 使用次数: {_useCount}");Array.Clear(Data); // 只重置数据内容return true;}public string GetId() => $"{_id} (使用次数: {_useCount})";}static void Main(string[] args){// 设置依赖注入var services = new ServiceCollection();// 注册对象池提供程序services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();// 注册 ReusableBuffer 的对象池services.AddSingleton<ObjectPool<ReusableBuffer>>(serviceProvider =>{var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();var policy = new DefaultPooledObjectPolicy<ReusableBuffer>();return provider.Create(policy);});var serviceProvider = services.BuildServiceProvider();// 获取对象池var bufferPool = serviceProvider.GetRequiredService<ObjectPool<ReusableBuffer>>();// 测试哈希计算Console.WriteLine("输入字符串计算哈希值 (输入 'exit' 退出):");while (true){Console.Write("\n请输入字符串: ");string input = Console.ReadLine() ?? "";if (input.ToLower() == "exit")break;var hash = CalculateHash(input, bufferPool);Console.WriteLine($"Hash: {hash}");}}static string CalculateHash(string input, ObjectPool<ReusableBuffer> bufferPool){var buffer = bufferPool.Get();Console.WriteLine($"获取缓冲区 ID: {buffer.GetId()}");try{// 将输入字符串转换为字节并存储在缓冲区中for (var i = 0; i < input.Length; i++){buffer.Data[i] = (byte)input[i];}Span<byte> hash = stackalloc byte[32];SHA256.HashData(buffer.Data.AsSpan(0, input.Length), hash);return Convert.ToHexString(hash);}finally{// 归还缓冲区到对象池Console.WriteLine($"归还缓冲区 ID: {buffer.GetId()}");bufferPool.Return(buffer);}}
}
程序结果:
输入字符串计算哈希值 (输入 'exit' 退出):请输入字符串: Hello World
创建新的缓冲区 ID: f493ed
获取缓冲区 ID: f493ed (使用次数: 0)
归还缓冲区 ID: f493ed (使用次数: 0)
重置缓冲区 ID: f493ed, 使用次数: 1
Hash: A591A6D40BF420404A011733CFB7B190D62C65BF0BCDA32B57B277D9AD9F146E请输入字符串: Test123
获取缓冲区 ID: f493ed (使用次数: 1)
归还缓冲区 ID: f493ed (使用次数: 1)
重置缓冲区 ID: f493ed, 使用次数: 2
Hash: D9B5F58F0B38198293971865A14074F59EBA3E82595BECBE86AE51F1D9F1F65E请输入字符串: exit
扩展类
前面我们通过实现IResettable接口来自动回收重用,如果需要自定义对象创建、复杂初始化或自定义回收重用逻辑的场景,通过策略模式实现,相比通过接口实现,扩展性更强
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System;namespace Microsoft.Extensions.ObjectPool
{internal class PooledObjectByFuncPolicy<T> : PooledObjectPolicy<T> where T : class{private readonly Func<T> _createfunc;private readonly Func<T, bool> _returnfunc;private static int _returnCount = 0;private static int _createCount = 0; // 添加创建计数public PooledObjectByFuncPolicy(Func<T> createfunc, Func<T, bool> returnfunc){_createfunc = createfunc;_returnfunc = returnfunc;}public override T Create(){int count = Interlocked.Increment(ref _createCount);Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Policy: 创建新对象 (第{count}次创建)");return _createfunc.Invoke();}public override bool Return(T obj){int returnCount = Interlocked.Increment(ref _returnCount);bool canReuse = _returnfunc.Invoke(obj);Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Policy: 对象返回池中 " +$"(第{returnCount}次返回, {(canReuse ? "可以重用" : "不可重用")})");return canReuse;}}public static class ObjectPoolExtension{/// <summary>/// 添加类型为<typeparamref name="T"/>至对象池进行复用/// </summary>/// <typeparam name="T"></typeparam>/// <param name="services"></param>/// <returns></returns>public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) where T : class,new (){return services.AddSingleton(s =>{var provider = s.GetRequiredService<ObjectPoolProvider>();return provider.Create<T>();});}/// <summary>/// 将<typeparamref name="T"/>类型添加至对象池, 并使用<paramref name="_create"/>进行创建初始化, 使用<paramref name="_returnfunc"/>归还/// </summary>/// <typeparam name="T"></typeparam>/// <param name="services"></param>/// <param name="_create"></param>/// <param name="_returnfunc"></param>/// <returns></returns>public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create, Func<T, bool> _returnfunc) where T : class{return services.AddSingleton(s =>{var provider = s.GetRequiredService<ObjectPoolProvider>();return provider.Create(new PooledObjectByFuncPolicy<T>(_create, _returnfunc));});}/// <summary>/// 将<typeparamref name="T"/>类型添加至对象池, 并使用<paramref name="_create"/>进行创建初始化/// </summary>/// <typeparam name="T"></typeparam>/// <param name="services"></param>/// <param name="_create"></param>/// <returns></returns>public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create) where T : class{return services.AddObjectPool(_create, t => true);}}
}
- PooledObjectByFuncPolicy类
这是一个对象池策略类,负责:
- 通过传入的Func委托来创建对象
- 通过传入的Func<T, bool>委托来决定对象是否可以被回收重用
- ObjectPoolExtension类
提供了三个扩展方法,用于在依赖注入容器中注册对象池:
// 最简单的注册方式,适用于有无参构造函数的类
public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) where T : class, new()// 完整的注册方式,可以自定义对象的创建和回收逻辑
public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create, Func<T, bool> _returnfunc)// 简化版注册方式,只需要提供创建逻辑
public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create)
应用实例
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System;
using System.Threading.Tasks;namespace Microsoft.Extensions.ObjectPool
{internal class PooledObjectByFuncPolicy<T> : PooledObjectPolicy<T> where T : class{private readonly Func<T> _createfunc;private readonly Func<T, bool> _returnfunc;private static int _returnCount = 0;private static int _createCount = 0; // 添加创建计数public PooledObjectByFuncPolicy(Func<T> createfunc, Func<T, bool> returnfunc){_createfunc = createfunc;_returnfunc = returnfunc;}public override T Create(){int count = Interlocked.Increment(ref _createCount);Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Policy: 创建新对象 (第{count}次创建)");return _createfunc.Invoke();}public override bool Return(T obj){int returnCount = Interlocked.Increment(ref _returnCount);bool canReuse = _returnfunc.Invoke(obj);Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Policy: 对象返回池中 " +$"(第{returnCount}次返回, {(canReuse ? "可以重用" : "不可重用")})");return canReuse;}}public static class ObjectPoolExtension{public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) where T : class, new(){return services.AddSingleton(s =>{var provider = s.GetRequiredService<ObjectPoolProvider>();return provider.Create<T>();});}public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create, Func<T, bool> _returnfunc) where T : class{return services.AddSingleton(s =>{var provider = s.GetRequiredService<ObjectPoolProvider>();return provider.Create(new PooledObjectByFuncPolicy<T>(_create, _returnfunc));});}public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create) where T : class{return services.AddObjectPool(_create, t => true);}}// 模拟一个耗资源的对象public class ExpensiveObject : IDisposable{public string Id { get; }private bool _isDisposed;private static int _totalObjects = 0;private readonly int _objectNumber;public ExpensiveObject(){_objectNumber = Interlocked.Increment(ref _totalObjects);Id = Guid.NewGuid().ToString("N");// 模拟耗时的初始化Thread.Sleep(5000);Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 创建新对象 #{_objectNumber}: {Id}");}public void DoWork(){Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 对象 #{_objectNumber} ({Id}) 执行工作");}public void Dispose(){if (!_isDisposed){Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 对象 #{_objectNumber} ({Id}) 被释放");_isDisposed = true;}}}// 测试程序public class Program{static async Task Main(string[] args){// 1. 配置依赖注入var services = new ServiceCollection();// 注册默认对象池提供程序services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();// 使用自定义策略注册对象池services.AddObjectPool<ExpensiveObject>(() => new ExpensiveObject(),obj =>{Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 检查对象 {obj.Id} 是否可以重用");return true; // 是否允许重用});using var serviceProvider = services.BuildServiceProvider();// 2. 获取对象池var objectPool = serviceProvider.GetRequiredService<ObjectPool<ExpensiveObject>>();Console.WriteLine("开始测试对象池...\n");for (int i = 0; i < 5; i++){await Task.Run(() =>{Console.WriteLine($"\n[{DateTime.Now:HH:mm:ss}] 开始第 {i + 1} 次操作");// 从池中获取对象var obj = objectPool.Get();try{// 使用对象obj.DoWork();Thread.Sleep(100);// 模拟工作时间}finally{Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 准备归还对象到池中");// 归还对象到池中objectPool.Return(obj);}Console.WriteLine("-------------------");});}Console.WriteLine("\n测试完成!");}}
}
“是否允许重用”设置为ture时,结果如下:
开始测试对象池...[11:14:25] 开始第 1 次操作
[11:14:25] Policy: 创建新对象 (第1次创建)
[11:14:30] 创建新对象 #1: d1ef227f13f443c887574b0a826cf954
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第1次返回, 可以重用)
-------------------[11:14:30] 开始第 2 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第2次返回, 可以重用)
-------------------[11:14:30] 开始第 3 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第3次返回, 可以重用)
-------------------[11:14:30] 开始第 4 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第4次返回, 可以重用)
-------------------[11:14:30] 开始第 5 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第5次返回, 可以重用)
-------------------测试完成!
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 被释放
通过日志的时间,我们得知只有第一次创建对象比较耗时
“是否允许重用”设置为false时,结果如下:
开始测试对象池...[11:16:03] 开始第 1 次操作
[11:16:03] Policy: 创建新对象 (第1次创建)
[11:16:08] 创建新对象 #1: 80923f55ac2445d3a62685e4166ca436
[11:16:08] 对象 #1 (80923f55ac2445d3a62685e4166ca436) 执行工作
[11:16:08] 准备归还对象到池中
[11:16:08] 检查对象 80923f55ac2445d3a62685e4166ca436 是否可以重用
[11:16:08] Policy: 对象返回池中 (第1次返回, 不可重用)
[11:16:08] 对象 #1 (80923f55ac2445d3a62685e4166ca436) 被释放
-------------------[11:16:08] 开始第 2 次操作
[11:16:08] Policy: 创建新对象 (第2次创建)
[11:16:13] 创建新对象 #2: 5b15f09ff293427a93fe7dce01cf9d3e
[11:16:13] 对象 #2 (5b15f09ff293427a93fe7dce01cf9d3e) 执行工作
[11:16:13] 准备归还对象到池中
[11:16:13] 检查对象 5b15f09ff293427a93fe7dce01cf9d3e 是否可以重用
[11:16:13] Policy: 对象返回池中 (第2次返回, 不可重用)
[11:16:13] 对象 #2 (5b15f09ff293427a93fe7dce01cf9d3e) 被释放
-------------------[11:16:13] 开始第 3 次操作
[11:16:13] Policy: 创建新对象 (第3次创建)
[11:16:18] 创建新对象 #3: ef65bab6f6a142dbb3a16f7daa668514
[11:16:18] 对象 #3 (ef65bab6f6a142dbb3a16f7daa668514) 执行工作
[11:16:18] 准备归还对象到池中
[11:16:18] 检查对象 ef65bab6f6a142dbb3a16f7daa668514 是否可以重用
[11:16:18] Policy: 对象返回池中 (第3次返回, 不可重用)
[11:16:18] 对象 #3 (ef65bab6f6a142dbb3a16f7daa668514) 被释放
-------------------[11:16:18] 开始第 4 次操作
[11:16:18] Policy: 创建新对象 (第4次创建)
[11:16:23] 创建新对象 #4: 81b791bb198f4d618ef93a6567618cec
[11:16:23] 对象 #4 (81b791bb198f4d618ef93a6567618cec) 执行工作
[11:16:23] 准备归还对象到池中
[11:16:23] 检查对象 81b791bb198f4d618ef93a6567618cec 是否可以重用
[11:16:23] Policy: 对象返回池中 (第4次返回, 不可重用)
[11:16:23] 对象 #4 (81b791bb198f4d618ef93a6567618cec) 被释放
-------------------[11:16:23] 开始第 5 次操作
[11:16:23] Policy: 创建新对象 (第5次创建)
[11:16:28] 创建新对象 #5: 60d4737360464aa3bb16d2894df3386c
[11:16:28] 对象 #5 (60d4737360464aa3bb16d2894df3386c) 执行工作
[11:16:28] 准备归还对象到池中
[11:16:28] 检查对象 60d4737360464aa3bb16d2894df3386c 是否可以重用
[11:16:28] Policy: 对象返回池中 (第5次返回, 不可重用)
[11:16:28] 对象 #5 (60d4737360464aa3bb16d2894df3386c) 被释放
-------------------测试完成!
通知日志,可以看出,每次都会创建新对象,非常耗时
总结
对象池的核心是对象重用,而对象池的目的是避免频繁创建和销毁对象
参考
- 通过 ASP.NET Core 中的 ObjectPool 重用对象 | Microsoft Learn
- IoTSharp/IoTSharp.Extensions.DependencyInjection/ObjectPoolExtension.cs at master · IoTSharp/IoTSharp