CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
.NET对象池的使用
1
.NetCore
Csharp
小笨蛋
发布于:2021年07月01日
更新于:2021年07月01日
166
#custom-toc-container
池这个概念大家都很熟悉,比如我们经常听到数据库连接池和线程池。它是一种基于使用预先分配资源集合的性能优化思想。 简单说,对象池就是对象的容器,旨在优化资源的使用,通过在一个容器中池化对象,并根据需要重复使用这些池化对象来满足性能上的需求。当一个对象被激活时,便被从池中取出。当对象被停用时,它又被放回池中,等待下一个请求。对象池一般用于对象的初始化过程代价较大或使用频率较高的场景。 那在 .NET 中如何实现或使用对象池呢? 在 ASP.NET Core 框架里已经内置了一个对象池功能的实现:`Microsoft.Extensions.ObjectPool`。如果是控制台应用程序,可以单独安装这个扩展库。 ### 1.池化策略 首先,要使用 `ObjectPool`,需要创建一个池化策略,告诉对象池你将如何创建对象,以及如何归还对象。 该策略通过实现接口 `IPooledObjectPolicy` 来定义,下面是一个最简单的策略实现: ```csharp public class FooPooledObjectPolicy : IPooledObjectPolicy
{ public Foo Create() { return new Foo(); } public bool Return(Foo obj) { return true; } } ``` 如果每次编码都要定义这样的策略,会比较麻烦,可以自己定义一个通用的泛型实现。`Microsoft.Extensions.ObjectPool` 中也提供了一个默认的泛型实现:`DefaultPooledObjectPolicy`。如果不需要定义复杂的构造逻辑,使用默认的就行。下面我们来看看怎么使用。 ### 2.对象池的使用 对象池使用的原则是:**有借有还,再借不难。** ![图片alt](/uploads/images/20210701/210926-083fbb6ad8774b9891dd7868bc808d28.png ''图片title'') 当对象池中没有实例时,则创建实例并返回给调用组件;当对象池中已有实例时,则直接取一个现有实例返回给调用组件。而且这个过程是线程安全的。 `Microsoft.Extensions.ObjectPool` 提供了默认的对象池实现:`DefaultObjectPool`,它提供了借 `Get` 和还 `Return` 操作接口。创建对象池时需要提供池化策略 `IPooledObjectPolicy` 作为其构造参数。 ```csharp var policy = new DefaultPooledObjectPolicy(); var pool = new DefaultObjectPool(policy); ``` 我们来看一个常规示例(C# 9.0 单文件完整代码): ```csharp using Microsoft.Extensions.ObjectPool; using System; var policy = new DefaultPooledObjectPolicy(); var pool = new DefaultObjectPool(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"); } ``` 打印结果: ![图片alt](/uploads/images/20210701/211230-9b78ff9ea5694bbf88e80749a7feb71e.png ''图片title'') 通过打印的 `Id` 知道,`item1` 和 `item2` 是同一样对象。我们再来试试只借不还会是什么样子。 ![图片alt](/uploads/images/20210701/211324-cd6da4227b80452eab0eedffc0aed74d.png ''图片title'') 可以看到,两个对象是不同的实例。所以,当调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复使用,避免因构造新对象消耗过多资源。 ### 3.指定对象池容量 在创建 `DefaultObjectPool` 时,还可以指定第二个参数:对象池的容量。它表示最大可从该对象池取出的对象数量,指定数量以外的被取走的对象将不会被池化。我来演示一下,大家就知道什么意思了,请看示例: ```csharp using Microsoft.Extensions.ObjectPool; using System; var policy = new DefaultPooledObjectPolicy(); // 指定容量为 2。 var pool = new DefaultObjectPool(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 个。来看看打印结果: ![图片alt](/uploads/images/20210701/211438-148e87e291994011ac54dd5e7cd668d0.png ''图片title'') 我们看到,`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](https://docs.microsoft.com/en-us/aspnet/core/performance/objectpool "https://docs.microsoft.com/en-us/aspnet/core/performance/objectpool") 这个例子把 `StringBuilder` 做了池化。我这里就直接贴官方的例子了,为了更直观些,我把无关的代码简化掉了。 先定义一个中间件: ```csharp public class BirthdayMiddleware { private readonly RequestDelegate _next; public BirthdayMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context, ObjectPoolbuilderPool) { var stringBuilder = builderPool.Get(); try { stringBuilder.Append("Hi"); // 其它处理 await context.Response.WriteAsync(stringBuilder.ToString()); } finally // 即使出错也要保证归还对象 { builderPool.Return(stringBuilder); } } } ``` 在 `Startup` 中注册相应的服务和中间件: ```csharp public class Startup { public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton(); services.TryAddSingleton
(serviceProvider => { var provider = serviceProvider.GetRequiredService(); var policy = new StringBuilderPooledObjectPolicy(); return provider.Create(policy); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMiddleware(); } } ``` 这个示例用了 `DefaultObjectPoolProvider`,它是默认的对象池 `Provider`,所以你也可以自定义自己的对象池 `Provider`。 ### 5.总结 `Microsoft.Extensions.ObjectPool`提供的对象池功能还是挺灵活的。普通场景使用使用默认的池化策略、默认的对象池和默认的对象池提供者就可以满足需求,也可以自定义其中任意某部件来实现比较特殊或复杂的需求。 对象池的使用原则是:有借有还,再借不难。当调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复利用,避免因过多的对象初始化影响系统性能。
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024