C#中通过ObjectPool重用对象提高程序性能

环境说明:

  • .NET 8.0
  • Microsoft.Extensions.DependencyInjection v9.0.0
  • Microsoft.Extensions.ObjectPool v9.0.0

ObjectPool重用对象

先看微软官方文档的描述:

Microsoft.Extensions.ObjectPool它支持将一组对象保留在内存中以供重用,而不是允许对对象进行垃圾回收

如果要管理的对象具有以下特征,应用可能希望使用对象池:

  • 分配/初始化成本高昂
  • 表示有限资源
  • 可预见地频繁使用

比如使用对象池来重用StringBuilder实例。 StringBuilder分配并管理自己的缓冲区来保存字符数据。如果经常使用StringBuilder来实现功能,重用这些对象会带来性能优势

对象池并不总是能提高性能:

  • 除非对象的初始化成本很高,否则从池中获取对象通常较慢
  • 在池解除分配之前,池管理的对象无法解除分配
  • 仅在使用应用或库的真实场景收集性能数据后才使用对象池

注意:ObjectPool不限制分配的对象数量,但限制保留的对象数量

使用ObjectPool

调用Get获取对象,调用Return返回对象。 不必返回每个对象。 如果某个对象未返回,系统将对其进行垃圾回收

使用示例

这个示例展示了如何在控制台应用程序中使用对象池来重用大型缓冲区,避免重复分配内存,提高性能。每次计算哈希值时都会重用同一个缓冲区对象,而不是创建新的

要点:

  • ObjectPoolProvider添加到依赖项注入 (DI) 容器
  • 实现IResettable接口,以在返回到对象池时自动清除缓冲区的内容

IResettable接口的作用是当对象被返回到对象池时:

  • 对象池会自动调用TryReset()方法
  • 可以自定义清除缓冲区中的敏感数据
  • 为下次使用做准备
cs 复制代码
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System.Security.Cryptography;

class Program
{
    public class ReusableBuffer : IResettable
    {
        private readonly string _id = Guid.NewGuid().ToString("N").Substring(0, 6);
        private int _useCount = 0;
        public byte[] Data { get; } = new byte[1024 * 1024]; // 1 MB

        public ReusableBuffer()
        {
            Console.WriteLine($"创建新的缓冲区 ID: {_id}");
        }

        public bool TryReset()
        {
            _useCount++;
            Console.WriteLine($"重置缓冲区 ID: {_id}, 使用次数: {_useCount}");
            Array.Clear(Data); // 只重置数据内容
            return true;
        }

        public string GetId() => $"{_id} (使用次数: {_useCount})";
    }

    static void Main(string[] args)
    {
        // 设置依赖注入
        var services = new ServiceCollection();

        // 注册对象池提供程序
        services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();

        // 注册 ReusableBuffer 的对象池
        services.AddSingleton<ObjectPool<ReusableBuffer>>(serviceProvider =>
        {
            var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
            var policy = new DefaultPooledObjectPolicy<ReusableBuffer>();
            return provider.Create(policy);
        });

        var serviceProvider = services.BuildServiceProvider();

        // 获取对象池
        var bufferPool = serviceProvider.GetRequiredService<ObjectPool<ReusableBuffer>>();

        // 测试哈希计算
        Console.WriteLine("输入字符串计算哈希值 (输入 'exit' 退出):");

        while (true)
        {
            Console.Write("\n请输入字符串: ");
            string input = Console.ReadLine() ?? "";

            if (input.ToLower() == "exit")
                break;

            var hash = CalculateHash(input, bufferPool);
            Console.WriteLine($"Hash: {hash}");
        }
    }

    static string CalculateHash(string input, ObjectPool<ReusableBuffer> bufferPool)
    {
        var buffer = bufferPool.Get();
        Console.WriteLine($"获取缓冲区 ID: {buffer.GetId()}");

        try
        {
            // 将输入字符串转换为字节并存储在缓冲区中
            for (var i = 0; i < input.Length; i++)
            {
                buffer.Data[i] = (byte)input[i];
            }

            Span<byte> hash = stackalloc byte[32];
            SHA256.HashData(buffer.Data.AsSpan(0, input.Length), hash);
            return Convert.ToHexString(hash);
        }
        finally
        {
            // 归还缓冲区到对象池
            Console.WriteLine($"归还缓冲区 ID: {buffer.GetId()}");
            bufferPool.Return(buffer);
        }
    }
}

程序结果:

plaintext 复制代码
输入字符串计算哈希值 (输入 'exit' 退出):

请输入字符串: Hello World
创建新的缓冲区 ID: f493ed
获取缓冲区 ID: f493ed (使用次数: 0)
归还缓冲区 ID: f493ed (使用次数: 0)
重置缓冲区 ID: f493ed, 使用次数: 1
Hash: A591A6D40BF420404A011733CFB7B190D62C65BF0BCDA32B57B277D9AD9F146E

请输入字符串: Test123
获取缓冲区 ID: f493ed (使用次数: 1)
归还缓冲区 ID: f493ed (使用次数: 1)
重置缓冲区 ID: f493ed, 使用次数: 2
Hash: D9B5F58F0B38198293971865A14074F59EBA3E82595BECBE86AE51F1D9F1F65E

请输入字符串: exit

扩展类

前面我们通过实现IResettable接口来自动回收重用,如果需要自定义对象创建、复杂初始化或自定义回收重用逻辑的场景,通过策略模式实现,相比通过接口实现,扩展性更强

cs 复制代码
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System;


namespace Microsoft.Extensions.ObjectPool
{
    internal class PooledObjectByFuncPolicy<T> : PooledObjectPolicy<T> where T : class
    {
        private readonly Func<T> _createfunc;
        private readonly Func<T, bool> _returnfunc;
        private static int _returnCount = 0;
        private static int _createCount = 0;  // 添加创建计数

        public PooledObjectByFuncPolicy(Func<T> createfunc, Func<T, bool> returnfunc)
        {
            _createfunc = createfunc;
            _returnfunc = returnfunc;
        }

        public override T Create()
        {
            int count = Interlocked.Increment(ref _createCount);
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Policy: 创建新对象 (第{count}次创建)");
            return _createfunc.Invoke();
        }

        public override bool Return(T obj)
        {
            int returnCount = Interlocked.Increment(ref _returnCount);
            bool canReuse = _returnfunc.Invoke(obj);
            Console.WriteLine(
                $"[{DateTime.Now:HH:mm:ss}] Policy: 对象返回池中 " +
                $"(第{returnCount}次返回, {(canReuse ? "可以重用" : "不可重用")})");
            return canReuse;
        }
    }
    public static class ObjectPoolExtension
    {
        /// <summary>
        /// 添加类型为<typeparamref name="T"/>至对象池进行复用
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) where T : class,new ()
        {
            return services.AddSingleton(s =>
            {
                var provider = s.GetRequiredService<ObjectPoolProvider>();
                return provider.Create<T>();
            });
        }
        /// <summary>
        /// 将<typeparamref name="T"/>类型添加至对象池, 并使用<paramref name="_create"/>进行创建初始化, 使用<paramref name="_returnfunc"/>归还
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="services"></param>
        /// <param name="_create"></param>
        /// <param name="_returnfunc"></param>
        /// <returns></returns>
        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create, Func<T, bool> _returnfunc) where T : class
        {
            return services.AddSingleton(s =>
            {
                var provider = s.GetRequiredService<ObjectPoolProvider>();
                return provider.Create(new PooledObjectByFuncPolicy<T>(_create, _returnfunc));
            });
        }
        /// <summary>
        ///  将<typeparamref name="T"/>类型添加至对象池, 并使用<paramref name="_create"/>进行创建初始化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="services"></param>
        /// <param name="_create"></param>
        /// <returns></returns>
        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create) where T : class
        {
            return services.AddObjectPool(_create, t => true);
        }
    }
}
  1. PooledObjectByFuncPolicy类

这是一个对象池策略类,负责:

  • 通过传入的Func委托来创建对象
  • 通过传入的Func<T, bool>委托来决定对象是否可以被回收重用
  1. ObjectPoolExtension类

提供了三个扩展方法,用于在依赖注入容器中注册对象池:

cs 复制代码
// 最简单的注册方式,适用于有无参构造函数的类
public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) 
    where T : class, new()

// 完整的注册方式,可以自定义对象的创建和回收逻辑
public static IServiceCollection AddObjectPool<T>(
    this IServiceCollection services, 
    Func<T> _create, 
    Func<T, bool> _returnfunc)

// 简化版注册方式,只需要提供创建逻辑
public static IServiceCollection AddObjectPool<T>(
    this IServiceCollection services, 
    Func<T> _create)

应用实例

cs 复制代码
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using System;
using System.Threading.Tasks;

namespace Microsoft.Extensions.ObjectPool
{
    internal class PooledObjectByFuncPolicy<T> : PooledObjectPolicy<T> where T : class
    {
        private readonly Func<T> _createfunc;
        private readonly Func<T, bool> _returnfunc;
        private static int _returnCount = 0;
        private static int _createCount = 0;  // 添加创建计数

        public PooledObjectByFuncPolicy(Func<T> createfunc, Func<T, bool> returnfunc)
        {
            _createfunc = createfunc;
            _returnfunc = returnfunc;
        }

        public override T Create()
        {
            int count = Interlocked.Increment(ref _createCount);
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Policy: 创建新对象 (第{count}次创建)");
            return _createfunc.Invoke();
        }

        public override bool Return(T obj)
        {
            int returnCount = Interlocked.Increment(ref _returnCount);
            bool canReuse = _returnfunc.Invoke(obj);
            Console.WriteLine(
                $"[{DateTime.Now:HH:mm:ss}] Policy: 对象返回池中 " +
                $"(第{returnCount}次返回, {(canReuse ? "可以重用" : "不可重用")})");
            return canReuse;
        }
    }

    public static class ObjectPoolExtension
    {
        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services) where T : class, new()
        {
            return services.AddSingleton(s =>
            {
                var provider = s.GetRequiredService<ObjectPoolProvider>();
                return provider.Create<T>();
            });
        }

        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create, Func<T, bool> _returnfunc) where T : class
        {
            return services.AddSingleton(s =>
            {
                var provider = s.GetRequiredService<ObjectPoolProvider>();
                return provider.Create(new PooledObjectByFuncPolicy<T>(_create, _returnfunc));
            });
        }

        public static IServiceCollection AddObjectPool<T>(this IServiceCollection services, Func<T> _create) where T : class
        {
            return services.AddObjectPool(_create, t => true);
        }
    }
    // 模拟一个耗资源的对象
    public class ExpensiveObject : IDisposable
    {
        public string Id { get; }
        private bool _isDisposed;
        private static int _totalObjects = 0;
        private readonly int _objectNumber;

        public ExpensiveObject()
        {
            _objectNumber = Interlocked.Increment(ref _totalObjects);
            Id = Guid.NewGuid().ToString("N");
            // 模拟耗时的初始化
            Thread.Sleep(5000);
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 创建新对象 #{_objectNumber}: {Id}");
        }

        public void DoWork()
        {
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 对象 #{_objectNumber} ({Id}) 执行工作");
        }

        public void Dispose()
        {
            if (!_isDisposed)
            {
                Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 对象 #{_objectNumber} ({Id}) 被释放");
                _isDisposed = true;
            }
        }
    }

    // 测试程序
    public class Program
    {
        static async Task Main(string[] args)
        {
            // 1. 配置依赖注入
            var services = new ServiceCollection();
            // 注册默认对象池提供程序
            services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();

            // 使用自定义策略注册对象池
            services.AddObjectPool<ExpensiveObject>(
                () => new ExpensiveObject(),
                obj =>
                {
                    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 检查对象 {obj.Id} 是否可以重用");
                    return true; // 是否允许重用
                }
            );

            using var serviceProvider = services.BuildServiceProvider();

            // 2. 获取对象池
            var objectPool = serviceProvider.GetRequiredService<ObjectPool<ExpensiveObject>>();

            Console.WriteLine("开始测试对象池...\n");

            for (int i = 0; i < 5; i++)
            {
                await Task.Run(() =>
                {
                    Console.WriteLine($"\n[{DateTime.Now:HH:mm:ss}] 开始第 {i + 1} 次操作");
                    // 从池中获取对象
                    var obj = objectPool.Get();
                    try
                    {
                        // 使用对象
                        obj.DoWork();
                        Thread.Sleep(100);// 模拟工作时间
                    }
                    finally
                    {
                        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 准备归还对象到池中");
                        // 归还对象到池中
                        objectPool.Return(obj);
                    }
                    Console.WriteLine("-------------------");
                });
            }
            Console.WriteLine("\n测试完成!");
        }
    }
}

"是否允许重用"设置为ture时,结果如下:

plaintext 复制代码
开始测试对象池...


[11:14:25] 开始第 1 次操作
[11:14:25] Policy: 创建新对象 (第1次创建)
[11:14:30] 创建新对象 #1: d1ef227f13f443c887574b0a826cf954
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第1次返回, 可以重用)
-------------------

[11:14:30] 开始第 2 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第2次返回, 可以重用)
-------------------

[11:14:30] 开始第 3 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第3次返回, 可以重用)
-------------------

[11:14:30] 开始第 4 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第4次返回, 可以重用)
-------------------

[11:14:30] 开始第 5 次操作
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 执行工作
[11:14:30] 准备归还对象到池中
[11:14:30] 检查对象 d1ef227f13f443c887574b0a826cf954 是否可以重用
[11:14:30] Policy: 对象返回池中 (第5次返回, 可以重用)
-------------------

测试完成!
[11:14:30] 对象 #1 (d1ef227f13f443c887574b0a826cf954) 被释放

通过日志的时间,我们得知只有第一次创建对象比较耗时

"是否允许重用"设置为false时,结果如下:

plaintext 复制代码
开始测试对象池...


[11:16:03] 开始第 1 次操作
[11:16:03] Policy: 创建新对象 (第1次创建)
[11:16:08] 创建新对象 #1: 80923f55ac2445d3a62685e4166ca436
[11:16:08] 对象 #1 (80923f55ac2445d3a62685e4166ca436) 执行工作
[11:16:08] 准备归还对象到池中
[11:16:08] 检查对象 80923f55ac2445d3a62685e4166ca436 是否可以重用
[11:16:08] Policy: 对象返回池中 (第1次返回, 不可重用)
[11:16:08] 对象 #1 (80923f55ac2445d3a62685e4166ca436) 被释放
-------------------

[11:16:08] 开始第 2 次操作
[11:16:08] Policy: 创建新对象 (第2次创建)
[11:16:13] 创建新对象 #2: 5b15f09ff293427a93fe7dce01cf9d3e
[11:16:13] 对象 #2 (5b15f09ff293427a93fe7dce01cf9d3e) 执行工作
[11:16:13] 准备归还对象到池中
[11:16:13] 检查对象 5b15f09ff293427a93fe7dce01cf9d3e 是否可以重用
[11:16:13] Policy: 对象返回池中 (第2次返回, 不可重用)
[11:16:13] 对象 #2 (5b15f09ff293427a93fe7dce01cf9d3e) 被释放
-------------------

[11:16:13] 开始第 3 次操作
[11:16:13] Policy: 创建新对象 (第3次创建)
[11:16:18] 创建新对象 #3: ef65bab6f6a142dbb3a16f7daa668514
[11:16:18] 对象 #3 (ef65bab6f6a142dbb3a16f7daa668514) 执行工作
[11:16:18] 准备归还对象到池中
[11:16:18] 检查对象 ef65bab6f6a142dbb3a16f7daa668514 是否可以重用
[11:16:18] Policy: 对象返回池中 (第3次返回, 不可重用)
[11:16:18] 对象 #3 (ef65bab6f6a142dbb3a16f7daa668514) 被释放
-------------------

[11:16:18] 开始第 4 次操作
[11:16:18] Policy: 创建新对象 (第4次创建)
[11:16:23] 创建新对象 #4: 81b791bb198f4d618ef93a6567618cec
[11:16:23] 对象 #4 (81b791bb198f4d618ef93a6567618cec) 执行工作
[11:16:23] 准备归还对象到池中
[11:16:23] 检查对象 81b791bb198f4d618ef93a6567618cec 是否可以重用
[11:16:23] Policy: 对象返回池中 (第4次返回, 不可重用)
[11:16:23] 对象 #4 (81b791bb198f4d618ef93a6567618cec) 被释放
-------------------

[11:16:23] 开始第 5 次操作
[11:16:23] Policy: 创建新对象 (第5次创建)
[11:16:28] 创建新对象 #5: 60d4737360464aa3bb16d2894df3386c
[11:16:28] 对象 #5 (60d4737360464aa3bb16d2894df3386c) 执行工作
[11:16:28] 准备归还对象到池中
[11:16:28] 检查对象 60d4737360464aa3bb16d2894df3386c 是否可以重用
[11:16:28] Policy: 对象返回池中 (第5次返回, 不可重用)
[11:16:28] 对象 #5 (60d4737360464aa3bb16d2894df3386c) 被释放
-------------------

测试完成!

通知日志,可以看出,每次都会创建新对象,非常耗时

总结

对象池的核心是对象重用,而对象池的目的是避免频繁创建和销毁对象

参考

相关推荐
凌盛羽1 小时前
C#对Excel表csv文件的读写操作
开发语言·windows·物联网·microsoft·c#·excel
CV大法好3 小时前
刘铁猛p3 C# 控制台程序引用System.Windows.Forms报错,无法引用程序集 解决方法
开发语言·c#
梅洪5 小时前
ASP.NET Core API 前后端分离跨域
后端·bootstrap·asp.net
小码编匠7 小时前
C#上位机实现高效示波器功能
后端·c#·.net
小码编匠7 小时前
WPF 制作雷达扫描图
后端·c#·.net
qq_10799104057 小时前
E172 ASP.NET+SQL+C#+LW+图书管理系统的设计与实现 配置 源码 文档 全套资料
sql·c#·asp.net
老牛源码8 小时前
2290 ASP.NET+SQL+LW+基于.NET旅游网站系统的开发与实现 源码 配置 文档
sql·asp.net·.net·旅游
我不是程序猿儿8 小时前
【C#】反射 和 特性(Attribute)、[AttributeUsage(AttributeTargets.Property)]
开发语言·c#