昨天在『.NET 大牛之路』技术群和大家聊到了对象池的话题,今天展开详细讲讲这个知识点。

这个概念大家都很熟悉,比如我们经常听到数据库连接池和线程池。它是一种基于使用预先分配资源集合的性能优化思想。

简单说,对象池就是对象的容器,旨在优化资源的使用,通过在一个容器中池化对象,并根据需要重复使用这些池化对象来满足性能上的需求。当一个对象被激活时,便被从池中取出。当对象被停用时,它又被放回池中,等待下一个请求。对象池一般用于对象的初始化过程代价较大或使用频率较高的场景。

那在 .NET 中如何实现或使用对象池呢?

在 ASP.NET Core 框架里已经内置了一个对象池功能的实现:Microsoft.Extensions.ObjectPool。如果是控制台应用程序,可以单独安装这个扩展库。

池化策略

首先,要使用 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>。如果不需要定义复杂的构造逻辑,使用默认的就行。下面我们来看看怎么使用。

对象池的使用

对象池使用的原则是:有借有还,再借不难。

当对象池中没有实例时,则创建实例并返回给调用组件;当对象池中已有实例时,则直接取一个现有实例返回给调用组件。而且这个过程是线程安全的。

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 知道,item1item2 是同一样对象。我们再来试试只借不还会是什么样子。

可以看到,两个对象是不同的实例。所以,当调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复使用,避免因构造新对象消耗过多资源。

指定对象池容量

在创建 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 个。来看看打印结果:

我们看到,item1item4 是同一个对象,item2item5 是同一个对象。item3item6 却不是同一个对象。

也就是说,当对象从池中取出超过指定容量的对象数量,虽然归还了相同数量的对象,但对象池只允许容纳 2 个对象,第三个对象不会被池化。

在 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。

总结

Microsoft.Extensions.ObjectPool 提供的对象池功能还是挺灵活的。普通场景使用使用默认的池化策略、默认的对象池和默认的对象池提供者就可以满足需求,也可以自定义其中任意某部件来实现比较特殊或复杂的需求。

对象池的使用原则是:有借有还,再借不难。当调用组件从对象池中借走一个对象实例,使用完后应立即归还给对象池,以便重复利用,避免因过多的对象初始化影响系统性能。

.NET Core 对象池的使用的更多相关文章

  1. .net core中的对象池

    asp.net core中通过扩展库的方式提供给了一个标准的对象池ObjectPool,定义在Microsoft.Extensions.ObjectPool.dll 程序集中.它本身是个纯虚的抽象类, ...

  2. 大数据技术之_27_电商平台数据分析项目_02_预备知识 + Scala + Spark Core + Spark SQL + Spark Streaming + Java 对象池

    第0章 预备知识0.1 Scala0.1.1 Scala 操作符0.1.2 拉链操作0.2 Spark Core0.2.1 Spark RDD 持久化0.2.2 Spark 共享变量0.3 Spark ...

  3. 对象池在 .NET (Core)中的应用[1]: 编程体验

    借助于有效的自动化垃圾回收机制,.NET让开发人员不在关心对象的生命周期,但实际上很多性能问题都来源于GC.并不说.NET的GC有什么问题,而是对象生命周期的跟踪和管理本身是需要成本的,不论交给应用还 ...

  4. 对象池在 .NET (Core)中的应用[2]: 设计篇

    <编程篇>已经涉及到了对象池模型的大部分核心接口和类型.对象池模型其实是很简单的,不过其中有一些为了提升性能而刻意为之的实现细节倒是值得我们关注.总的来说,对象池模型由三个核心对象构成,它 ...

  5. Object Pooling(对象池)实现

    在文章开始之前首先要思考的问题是为什么要建立对象池.这和.NET垃圾回收机制有关,正如下面引用所说,内存不是无限的,垃圾回收器最终要回收对象,释放内存.尽管.NET为垃圾回收已经进行了大量优化,例如将 ...

  6. Java 基础 - 对象池

    对象池  优点:  防止过多的创建对象合理利用对象, 缺点: 会有线程阻塞 Demo 测试代码 package com.cjcx.pay.obj; import java.util.Enumerati ...

  7. ObjectPool 对象池设计模式

    Micosoft.Extension.ObjectPool 源码架构.模式分析: 三大基本对象: ObjectPool抽象类 ObjectPoolProvider抽象类 IPooledObjectPo ...

  8. 设计模式之美:Object Pool(对象池)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):实现 DatabaseConnectionPool 类. 实现方式(二):使用对象构造方法和预分配方式实现 ObjectPool ...

  9. Egret中的对象池ObjectPool

    为了可以让对象复用,防止大量重复创建对象,导致资源浪费,使用对象池来管理. 对象池具体含义作用,自行百度. 一 对象池A 二 对象池B 三 字符串key和对象key的效率 一 对象池A /** * 对 ...

随机推荐

  1. 如何对你的Linux系统进行基准测试: 3开源基准测试工具

    如何对你的Linux系统进行基准测试: 3开源基准测试工具   0 赞0 评论 文章标签:SYS  Source  benchmark  tool  开源  基准  系统     linux实用程序的 ...

  2. openstack创建vlan网络并配置网络设备

    1.在管理员-->网络-->创建网络. 2.填写网络信息,这里要划分新的VLAN,注意在物理网络中填写的事VLAN,段ID指的是vlan的id 3.创建的网络. 4.创建子网,在里面修改子 ...

  3. 【Java】Files.readAllBytes(Path) 遇见的坑

    Files.readAllBytes(Path)方法把整个文件读入内存,此方法返回一个字节数组,还可以把结果传递给String的构造器,以便创建字符串输出. 在针对大文件的读取的时候,可能会出现内存不 ...

  4. 04丨MongoDB特色及优势

  5. Linux Access.conf安全配置

    access.conf is the configuration file used to logins to the Linux or Unix systems. This file is loca ...

  6. UnicodeDecodeError:'ascii' codec can't decode byte 0xe5 in position 89: ordinal not in range(128)

    环境python2,在出现该错误的python文件,增加: import sys reload(sys) sys.setdefaultencoding('utf8') 重新运行,不再报错

  7. GO学习-(2) 从零开始搭建Go语言开发环境

    从零开始搭建Go语言开发环境 一步一步,从零搭建Go语言开发环境. 安装Go语言及搭建Go语言开发环境 下载 下载地址 Go官网下载地址:https://golang.org/dl/ Go官方镜像站( ...

  8. 2020年Yann Lecun深度学习笔记(上)

    2020年Yann Lecun深度学习笔记(上)

  9. TVM 架构设计

    TVM 架构设计 本文面向希望了解TVM体系结构和/或积极参与项目开发的开发人员. 主要内容如下: 示例编译流程概述了TVM将模型的高级概念转换为可部署模块的步骤. 逻辑架构组件部分描述逻辑组件.针对 ...

  10. NOIP2018初赛普及组原题&题解

    NOIP2018初赛普及组原题&题解 目录 NOIP2018初赛普及组原题&题解 原题&答案 题解 单项选择题 第$1$题 第$2$题 第$3$题 第$4$题 第$5$题 第$ ...