c# .Net 对象池
对象池就是对象的容器,旨在优化资源的使用,通过在一个容器中池化对象,并根据需要重复使用这些池化对象来满足性能上的需求。当一个对象被激活时,便被从池中取出。当对象被停用时,它又被放回池中,等待下一个请求。
对象池一般用于对象的初始化过程代价较大或使用频率较高的场景。
在 ASP.NET Core 框架里(或者.net Framework高版本)已经内置了一个对象池功能的实现:Microsoft.Extensions.ObjectPool
。
池的策略与基本使用
首先,要使用 ObjectPool
,需要创建一个池化策略,告诉对象池你将如何创建对象,以及如何归还对象。
该策略通过实现接口 IPooledObjectPolicy
来定义,下面是一个最简单的策略实现:
public class DefaultPooledObjectPolicy<T> : PooledObjectPolicy<T> where T : class, new() { /// <inheritdoc /> public override T Create() { return new T(); } /// <inheritdoc /> public override bool Return(T obj) { if (obj is IResettable resettable) { return resettable.TryReset(); } return true; } }
当对象池中没有实例时,则创建实例并返回给调用组件;当对象池中已有实例时,则直接取一个现有实例返回给调用组件。而且这个过程是线程安全的。
Microsoft.Extensions.ObjectPool
提供了默认的对象池实现:DefaultObjectPool<T>
,它提供了借 Get
和还 Return
操作接口。创建对象池时需要提供池化策略 IPooledObjectPolicy<T>
作为其构造参数。
下面是一个默认对象池的基本使用
public class AutoMan { public int Id { get; set; } public DateTime CreateTime { get; set; } public AutoMan() { Thread.Sleep(5000);//模拟对象耗时创建 Id = new Random().Next(); CreateTime = DateTime.Now; } }
var policy = new DefaultPooledObjectPolicy<AutoMan>(); var pool = new DefaultObjectPool<AutoMan>(policy); Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < 10; i++) { var m = pool.Get(); Console.WriteLine(i + "创建成功" + "id:" + m.Id + "用时:" + stopwatch.ElapsedMilliseconds); stopwatch.Restart(); if (i >= 2)//前三个不归还到对象池 { pool.Return(m); } }
运行结果如图:
调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复使用,避免因构造新对象消耗过多资源。
另外,还可以在DefaultObjectPool构造函数中指定对象池的容量。
总结
对象池的使用原则是:有借有还,再借不难。当调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复利用,避免因过多的对象初始化影响系统性能。
对象池主要用在对象初始化比较耗时和使用比较频繁的场景,比如初始化时要读取网络资源,有时候这些对象因为有时效性,又不能用单例(后面会通过自定义策略来说明)。所以说,IOC并不能完全替代对象池。
以上知识来自于:.NET Core 对象池的使用 - 精致码农 - 博客园 (cnblogs.com)
对象池的预热和对象池大小的控制
像缓存一样,为了提高性能,我们可以在系统初始化的时候进行预热。
static void MaxPoolCount() { var poolObjCount = 10; var policy = new DefaultPooledObjectPolicy<AutoMan>(); var pool = new DefaultObjectPool<AutoMan>(policy, poolObjCount);//对象池容量为10 List<AutoMan> list = new List<AutoMan>(); Console.WriteLine("开始-----------------------------------"); for (int i = 0; i < poolObjCount; i++) { var m = pool.Get(); Console.WriteLine("增加一个对象池对象"); list.Add(m); } foreach (AutoMan m in list) { pool.Return(m); } Console.WriteLine("预热结束-----------------------------------"); List<Task> tasks = new List<Task>(); var getCount = 20; for (int i = 0; i < getCount; i++) { tasks.Add(Task.Run(() => { Stopwatch stopwatch = Stopwatch.StartNew(); var m = pool.Get(); stopwatch.Stop(); Console.WriteLine("线程id:" + Thread.CurrentThread.ManagedThreadId + "创建成功,用时:" + stopwatch.ElapsedMilliseconds); })); } Task.WaitAll(tasks.ToArray()); }
运行结果:
通过上述运行结果可以得知:并发请求数超过了对象池数量会导致重新创建对象。
自定义池策略
通过自定义池策略,可以加深对象池的理解,并可以清晰认识到他跟IOC使用场景的区别。
/// <summary> /// 自定义对象池策略 /// </summary> /// <typeparam name="T"></typeparam> public class CustomPooledObjectPolicy : DefaultPooledObjectPolicy<AutoMan> { public override AutoMan Create() { return base.Create(); } public override bool Return(AutoMan obj) { if (obj == null) { return false; } //对象30秒过期 if (obj.CreateTime.AddSeconds(30) < DateTime.Now) { return false; } return base.Return(obj); } }
static void CustomPolicy() { var poolObjCount = 10; var policy = new CustomPooledObjectPolicy(); var pool = new DefaultObjectPool<AutoMan>(policy, poolObjCount); List<AutoMan> list = new List<AutoMan>(); for (int i = 0; i < poolObjCount; i++) { var m = pool.Get(); Console.WriteLine("增加一个对象池对象"); list.Add(m); } //等待40秒,使得归还失败 Thread.Sleep(40000); foreach (AutoMan m in list) { pool.Return(m); } Console.WriteLine("预热结束-----------------------------------"); List<Task> tasks = new List<Task>(); for (int i = 0; i < 5; i++) { tasks.Add(Task.Run(() => { Stopwatch stopwatch = Stopwatch.StartNew(); var m = pool.Get(); stopwatch.Stop(); Console.WriteLine("线程id:" + Thread.CurrentThread.ManagedThreadId + "创建成功,用时:" + stopwatch.ElapsedMilliseconds); })); } Task.WaitAll(tasks.ToArray()); }
个人理解:如果不需要自定义策略,那使用IOC的单例跟这个对象池是没有区别的。但是需要自定义对象的创建和回收策略,那需要使用对象池