.NET对象池的使用
以下文章来源于精致码农 ,作者liamwang
昨天在『.NET 大牛之路』技术群和大家聊到了对象池的话题,今天展开详细讲讲这个知识点。
池这个概念大家都很熟悉,比如我们经常听到数据库连接池和线程池。它是一种基于使用预先分配资源集合的性能优化思想。
简单说,对象池就是对象的容器,旨在优化资源的使用,通过在一个容器中池化对象,并根据需要重复使用这些池化对象来满足性能上的需求。当一个对象被激活时,便被从池中取出。当对象被停用时,它又被放回池中,等待下一个请求。对象池一般用于对象的初始化过程代价较大或使用频率较高的场景。
那在 .NET 中如何实现或使用对象池呢?
在 ASP.NET Core 框架里已经内置了一个对象池功能的实现:Microsoft.Extensions.ObjectPool
。如果是控制台应用程序,可以单独安装这个扩展库。
1池化策略
首先,要使用 ObjectPool
,需要创建一个池化策略,告诉对象池你将如何创建对象,以及如何归还对象。
该策略通过实现接口 IPooledObjectPolicy
来定义,下面是一个最简单的策略实现:
public class FooPooledObjectPolicy : IPooledObjectPolicy<Foo>
{
public Foo Create()
{
return new Foo();
}
public bool Return(Foo obj)
{
return true;
}
}
如果每次编码都要定义这样的策略,会比较麻烦,可以自己定义一个通用的泛型实现。Microsoft.Extensions.ObjectPool
中也提供了一个默认的泛型实现:DefaultPooledObjectPolicy<T>
。如果不需要定义复杂的构造逻辑,使用默认的就行。下面我们来看看怎么使用。
2对象池的使用
对象池使用的原则是:有借有还,再借不难。
当对象池中没有实例时,则创建实例并返回给调用组件;当对象池中已有实例时,则直接取一个现有实例返回给调用组件。而且这个过程是线程安全的。
Microsoft.Extensions.ObjectPool
提供了默认的对象池实现:DefaultObjectPool<T>
,它提供了借 Get
和还 Return
操作接口。创建对象池时需要提供池化策略 IPooledObjectPolicy<T>
作为其构造参数。
var policy = new DefaultPooledObjectPolicy<Foo>();
var pool = new DefaultObjectPool<Foo>(policy);
我们来看一个常规示例(C# 9.0 单文件完整代码):
using Microsoft.Extensions.ObjectPool;
using System;
var policy = new DefaultPooledObjectPolicy<Foo>();
var pool = new DefaultObjectPool<Foo>(policy);
// 借
var item1 = pool.Get();
// 还
pool.Return(item1);
Console.WriteLine("item 1: {0}", item1.Id);
// 借
var item2 = pool.Get();
// 还
pool.Return(item2);
Console.WriteLine("item 2: {0}", item2.Id);
Console.ReadKey();
public class Foo
{
public string Id { get; set; } = Guid.NewGuid().ToString("N");
}
打印结果:
通过打印的 Id 知道,item1
和 item2
是同一样对象。我们再来试试只借不还会是什么样子。
可以看到,两个对象是不同的实例。所以,当调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复使用,避免因构造新对象消耗过多资源。
3指定对象池容量
在创建 DefaultObjectPool<T>
时,还可以指定第二个参数:对象池的容量。它表示最大可从该对象池取出的对象数量,指定数量以外的被取走的对象将不会被池化。我来演示一下,大家就知道什么意思了,请看示例:
using Microsoft.Extensions.ObjectPool;
using System;
var policy = new DefaultPooledObjectPolicy<Foo>();
// 指定容量为 2。
var pool = new DefaultObjectPool<Foo>(policy, 2);
// 借走 3 个
var item1 = pool.Get();
Console.WriteLine("item 1: {0}", item1.Id);
var item2 = pool.Get();
Console.WriteLine("item 2: {0}", item2.Id);
var item3 = pool.Get();
Console.WriteLine("item 3: {0}", item3.Id);
// 再还会 3 个
pool.Return(item1);
pool.Return(item2);
pool.Return(item3);
// 再借走 3 个
var item4 = pool.Get();
Console.WriteLine("item 4: {0}", item4.Id);
var item5 = pool.Get();
Console.WriteLine("item 5: {0}", item5.Id);
var item6 = pool.Get();
Console.WriteLine("item 6: {0}", item6.Id);
Console.ReadKey();
注意示例代码中我给对象池指定了容量为 2,然后借走 3 个再归还 3 个,后面再借走 3 个。来看看打印结果:
我们看到,item1
与 item4
是同一个对象,item2
与 item5
是同一个对象。item3
与 item6
却不是同一个对象。
也就是说,当对象从池中取出超过指定容量的对象数量,虽然归还了相同数量的对象,但对象池只允许容纳 2 个对象,第三个对象不会被池化。
4在 ASP.NET Core 中使用
ASP.NET Core 框架内置好了 Microsoft.Extensions.ObjectPool
,不需要单独安装。官方文档有个基于 ASP.NET Core 的使用示例:
https://docs.microsoft.com/en-us/aspnet/core/performance/objectpool
这个例子把 StringBuilder
做了池化。我这里就直接贴官方的例子了,为了更直观些,我把无关的代码简化掉了。
先定义一个中间件:
public class BirthdayMiddleware
{
private readonly RequestDelegate _next;
public BirthdayMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, ObjectPool<StringBuilder> builderPool)
{
var stringBuilder = builderPool.Get();
try
{
stringBuilder.Append("Hi");
// 其它处理
await context.Response.WriteAsync(stringBuilder.ToString());
}
finally // 即使出错也要保证归还对象
{
builderPool.Return(stringBuilder);
}
}
}
在 Startup
中注册相应的服务和中间件:
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)
{
app.UseMiddleware<BirthdayMiddleware>();
}
}
这个示例用了 DefaultObjectPoolProvider
,它是默认的对象池 Provider,所以你也可以自定义自己的对象池 Provider。
5总结
Microsoft.Extensions.ObjectPool
提供的对象池功能还是挺灵活的。普通场景使用使用默认的池化策略、默认的对象池和默认的对象池提供者就可以满足需求,也可以自定义其中任意某部件来实现比较特殊或复杂的需求。
对象池的使用原则是:有借有还,再借不难。当调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复利用,避免因过多的对象初始化影响系统性能。
相关文章
- mysql connector 如何使用_MySQL Connector/Net 的简略使用
- .net 温故知新:【9】.NET日志记录 ILogger使用和原理
- 宝塔面板Linux系统通过Docker部署VB.NET Asp.Net Core WebAPI应用
- .net 温故知新:【10】.NET ORM框架EFCore使用入门之CodeFirs、DBFirst
- .Net 7 的 R2R,Crossgen2是什么?
- Linux.Net:开启新技术之旅(linux.net)
- 使用.NET技术操作MySQL数据库:简单易学,高效稳定(.net操作mysql)
- Net不再压着 MSSql,语音变得前景无限(net mssql语音)
- NET与MySQL的无限结合(.net 连接mysql)
- NET环境下MySQL数据库的使用实践(.net支持mysql吗)
- NET 与 MySQL 结合能实现优雅的数据事务处理(.net MySQL事物)
- REDIS锁技术提升NET应用性能(redis 锁 .net)
- 使用Oracle64位Net突破技术极限(oracle64位net)
- ASP.NET:ADO.NET的DataAdapter对象
- 答你所问.NET小常识方便学习asp.net的朋友
- .NET下运用策略模式(组合行为和实体的一种模式)
- Asp.net内置对象之Request对象(概述及应用)
- .net实现oracle数据库中获取新插入数据的id的方法
- .net调用存储过程详细介绍
- Asp.Net无刷新文件上传并显示进度条的实现方法及思路
- .Net下二进制形式的文件(图片)的存储与读取详细解析
- .net获取本机公网IP地址示例
- .net非托管资源的回收方法