前言
复用, 是一个重要的话题, 也是我们日常开发中经常遇到的, 不可避免的问题.
举个最为简单, 大家最为熟悉的例子, 数据库连接池, 就是复用数据库连接.
那么复用的意义在那里呢?
简单来说就是减少不必要的资源损耗.
除了数据库连接, 可能在不同的情景或需求下, 还会有很多其他对象需要进行复用, 这个时候就会有所谓的 Object Pool(对象池).
小伙伴们应该也自己实现过类似的功能, 或用 ConcurrentBag, 或用 ConcurrentQueue, 或用其他方案.
这也里分享一个在微软文档中的实现
How to: Create an Object Pool by Using a ConcurrentBag
当然, 在. NET Core 中, 微软已经帮我们实现了一个简单的 Object Pool.
我们只需要添加 Microsoft.Extensions.ObjectPool 的引用即可使用了.
Microsoft.Extensions.ObjectPool
Microsoft.Extensions.ObjectPool 可以说是. NET Core 的一个基础类库.
它位于 aspnet 的 https://github.com/aspnet/Common 项目中, 类型其他基础模块都有使用相关的功能, 也好比 https://github.com/aspnet/Routing/ 项目.
下面就简单看看它的用法.
在开始之前, 我们先定义一个可以复用的 object
- public class Demo
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public DateTime CreateTimte { get; set; }
- }
用法 1
- var defalutPolicy = new DefaultPooledObjectPolicy<Demo>();
- // 最大可保留对象数量 = Environment.ProcessorCount * 2
- var defaultPool = new DefaultObjectPool<Demo>(defalutPolicy);
- for (int i = 0; i <PoolSize; i++)
- {
- item1 = defaultPool.Get();
- Console.WriteLine($"#{i+1}-{item1.Id}-{item1.Name}-{item1.CreateTimte}");
- }
在创建 pool 之前, 我们要先定义一个 Policy. 这里直接用自带的 DefaultPooledObjectPolicy 来构造.
对象池会有一个维护的最大数量, 线程数.
通过 pool 对象的 Get 方法, 从对象池中取出一个对象.
上面代码运行结果
- #1-0--01/01/0001 00:00:00
- #2-0--01/01/0001 00:00:00
- #3-0--01/01/0001 00:00:00
- #4-0--01/01/0001 00:00:00
- #5-0--01/01/0001 00:00:00
- #6-0--01/01/0001 00:00:00
- #7-0--01/01/0001 00:00:00
- #8-0--01/01/0001 00:00:00
这个结果说明, Object Pool 中的对象都是直接 new 出来的, 并没有对一些属性进行贬值操作, 这个时候往往没有太多实际意义.
因为 DefaultPooledObjectPolicy 本来就是直接 new 了一个对象出来, 很多时候, 这并不是我们所期望的!
要想符合我们实际的使用, 就要自己定义一个 Policy!
下面来看看用法 2
用法 2
先定义一个 Policy, 实现 IPooledObjectPolicy 这个接口. T 很自然就是我们的 Demo 类了.
- public class DemoPooledObjectPolicy : IPooledObjectPolicy<Demo>
- {
- public Demo Create()
- {
- return new Demo { Id = 1, Name = "catcher", CreateTimte = DateTime.Now };
- }
- public bool Return(Demo obj)
- {
- return true;
- }
- }
这里要实现 Create 和 Return 两个方法.
见名知义, Create 方法就是用来创建 Demo 对象的, Return 方法就是将 Demo 对象扔回 Object Pool 的 (有借有还).
然后是用 DemoPooledObjectPolicy 去替换 DefaultPooledObjectPolicy.
- var demoPolicy = new DemoPooledObjectPolicy();
- var defaultPoolWithDemoPolicy = new DefaultObjectPool<Demo>(demoPolicy,1);
- // 借
- item1 = defaultPoolWithDemoPolicy.Get();
- // 还
- defaultPoolWithDemoPolicy.Return(item1);
- // 借, 但是不还
- item2 = defaultPoolWithDemoPolicy.Get();
- Console.WriteLine($"{item1.Id}-{item1.Name}-{item1.CreateTimte}");
- Console.WriteLine($"{item2.Id}-{item2.Name}-{item2.CreateTimte}");
- Console.WriteLine(item1 == item2);
- // 创建一个新的
- item3 = defaultPoolWithDemoPolicy.Get();
- Console.WriteLine($"{item3.Id}-{item3.Name}-{item3.CreateTimte}");
- Console.WriteLine(item3 == item1);
这里定义了对象池只保留一个对象.
由于从 object pool 中取出来之后, 有一步还回去的操作, 所以 item1 和 item2 应当是同一个对象.
从 object pool 中拿出了 item2 之后, 它并没有还回去, 所以 object pool 会基于我们定义的 Policy 去创建一个新的对象出来.
下面是用法 2 的输出结果:
- 1-catcher-09/17/2018 22:32:38
- 1-catcher-09/17/2018 22:32:38
- True
- 1-catcher-09/17/2018 22:32:38
- False
可以看到 item1,item2 和 item3 的各个属性是一样的, 并且 item1 和 item2 确实是同一个对象. item3 和 item1 并不是同一个.
用法 3
除了 DefaultObjectPool 外, 还有 DefaultObjectPoolProvider 也可以创建一个 Object Pool.
创建一个 Object Pool, 一定是离不开 Policy 的, 所以这里还是用了我们自己定义的 DemoPooledObjectPolicy.
- var defaultProvider = new DefaultObjectPoolProvider();
- var policy = new DemoPooledObjectPolicy();
- //default maximumRetained is Environment.ProcessorCount * 2
- ObjectPool<Demo> pool = defaultProvider.Create(policy);
- item1 = pool.Get();
- pool.Return(item1);
- item2 = pool.Get();
- Console.WriteLine($"{item1.Id}-{item1.Name}-{item1.CreateTimte}");
- Console.WriteLine($"{item2.Id}-{item2.Name}-{item2.CreateTimte}");
- Console.WriteLine(item1 == item2);
- item3 = pool.Get();
- Console.WriteLine($"{item3.Id}-{item3.Name}-{item3.CreateTimte}");
- Console.WriteLine(item3 == item2);
用 Provider 创建 Object Pool 时, 不能指定保留的最大对象数量, 只能用的是默认的 Environment.ProcessorCount * 2.
后面的使用, 和用法 2 是一样的.
可以看到 item1 和 item2 是同一个对象. 从 Object Pool 中取对象的时候, 会取第一个, 所以还回去后, 再取的话, 还是会取到原来的第一个.
item3 那里是直接从 Object Pool 中取出来的, 没有再次创建, 因为这里的 Object Pool 维护着多个对象, 而不是用法 2 中的只有一个, 所以它是直接从 Pool 中拿的.
下面是输出结果
- 1-catcher-09/17/2018 22:38:34
- 1-catcher-09/17/2018 22:38:34
- True
- 1-catcher-09/17/2018 22:38:34
- False
和用法 2, 本质是一样的.
用法 4
好像上面的用法, 都不那么像我们正常使用的. 我们还是需要依赖注入的.
那么我们最后就来看看怎么结合依赖注入吧. 当然这里的本质还是离不开 Policy 和 Provider 这两个东西.
- IServiceCollection services = new ServiceCollection();
- services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
- services.AddSingleton(s =>
- {
- var provider = s.GetRequiredService<ObjectPoolProvider>();
- return provider.Create(new DemoPooledObjectPolicy());
- });
- ServiceProvider serviceProvider = services.BuildServiceProvider();
- var pool = serviceProvider.GetService<ObjectPool<Demo>>();
- item1 = pool.Get();
- pool.Return(item1);
- item2 = pool.Get();
- Console.WriteLine($"{item1.Id}-{item1.Name}-{item1.CreateTimte}");
- Console.WriteLine($"{item2.Id}-{item2.Name}-{item2.CreateTimte}");
- Console.WriteLine(item1 == item2);
- item3 = pool.Get();
- Console.WriteLine($"{item3.Id}-{item3.Name}-{item3.CreateTimte}");
- Console.WriteLine(item3 == item2);
我们首先需要完成对 Provider 的注册, 然后直接拿它的实例去创建一个 Object Pool 即可.
如果想在其他地方用, 通过构造函数注入即可.
这里的结果也是和前面一样的, 没什么好多说的.
总结
在这几种用法中, 我们最常用的应该是用法 4.
但是无论那种用法, 我们都需要了解, Object Pool 离不开 Pool,Policy 和 Provider 这三个家伙.
有了这三个, 或许我们就可以为所欲为了.
当然, 它还提供了几个特殊的东西, 有兴趣的可以去看看.
- LeakTrackingObjectPool
- StringBuilderPooledObjectPolicy
最后用一个脑图结束本文.
来源: https://www.cnblogs.com/catcher1994/p/9666944.html