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的单例跟这个对象池是没有区别的。但是需要自定义对象的创建和回收策略,那需要使用对象池

posted @ 2024-06-17 15:26  chenxizhaolu  阅读(16)  评论(0编辑  收藏  举报