mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(pool): 重构对象池系统增加统计和容量控制功能
- 引入 PoolInfo 类来管理对象池的核心数据结构和统计信息 - 添加对象池容量限制功能,超过容量时自动销毁多余对象 - 实现对象池统计功能,跟踪创建、获取、释放、销毁等操作计数 - 新增 GetPoolSize 和 GetActiveCount 方法获取池状态信息 - 添加 SetMaxCapacity 方法设置池的最大容量限制 - 实现 Prewarm 功能用于预创建对象提高性能 - 提供 GetStatistics 方法获取详细的池统计信息 - 添加 IsExternalInit 支持旧版 .NET 框架的 init-only 属性 - 扩展 ArrayPool 添加便捷的扩展方法和自动释放功能 - 新增 StringBuilderPool 提供高性能的字符串构建器复用 - 完善单元测试覆盖新增的所有功能特性
This commit is contained in:
parent
1d50dc2224
commit
eb763a9bc4
18
GFramework.Core.Abstractions/internals/IsExternalInit.cs
Normal file
18
GFramework.Core.Abstractions/internals/IsExternalInit.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// IsExternalInit.cs
|
||||
// This type is required to support init-only setters and record types
|
||||
// when targeting netstandard2.0 or older frameworks.
|
||||
|
||||
#if !NET5_0_OR_GREATER
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace System.Runtime.CompilerServices;
|
||||
|
||||
/// <summary>
|
||||
/// 提供一个占位符类型,用于支持 C# 9.0 的 init 访问器功能。
|
||||
/// 该类型在 .NET 5.0 及更高版本中已内置,因此仅在较低版本的 .NET 中定义。
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class IsExternalInit
|
||||
{
|
||||
}
|
||||
#endif
|
||||
@ -27,4 +27,39 @@ public interface IObjectPoolSystem<in TKey, TObject>
|
||||
/// 清空所有对象池
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定池的当前大小
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <returns>池中可用对象的数量</returns>
|
||||
int GetPoolSize(TKey key);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定池的活跃对象数量
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <returns>已被获取但未释放的对象数量</returns>
|
||||
int GetActiveCount(TKey key);
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定池的最大容量
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <param name="maxCapacity">最大容量,超过此容量的对象将被销毁而不是放回池中</param>
|
||||
void SetMaxCapacity(TKey key, int maxCapacity);
|
||||
|
||||
/// <summary>
|
||||
/// 预热对象池,提前创建指定数量的对象
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <param name="count">要预创建的对象数量</param>
|
||||
void Prewarm(TKey key, int count);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定池的统计信息
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <returns>池的统计信息</returns>
|
||||
PoolStatistics GetStatistics(TKey key);
|
||||
}
|
||||
42
GFramework.Core.Abstractions/pool/PoolStatistics.cs
Normal file
42
GFramework.Core.Abstractions/pool/PoolStatistics.cs
Normal file
@ -0,0 +1,42 @@
|
||||
namespace GFramework.Core.Abstractions.pool;
|
||||
|
||||
/// <summary>
|
||||
/// 对象池统计信息
|
||||
/// </summary>
|
||||
public class PoolStatistics
|
||||
{
|
||||
/// <summary>
|
||||
/// 池中当前可用对象数量
|
||||
/// </summary>
|
||||
public int AvailableCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前活跃(已获取但未释放)的对象数量
|
||||
/// </summary>
|
||||
public int ActiveCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 池的最大容量限制,0 表示无限制
|
||||
/// </summary>
|
||||
public int MaxCapacity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计创建的对象总数
|
||||
/// </summary>
|
||||
public int TotalCreated { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计获取对象的次数
|
||||
/// </summary>
|
||||
public int TotalAcquired { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计释放对象的次数
|
||||
/// </summary>
|
||||
public int TotalReleased { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计销毁的对象数量(超过容量限制被销毁的对象)
|
||||
/// </summary>
|
||||
public int TotalDestroyed { get; init; }
|
||||
}
|
||||
194
GFramework.Core.Tests/extensions/ArrayPoolExtensionsTests.cs
Normal file
194
GFramework.Core.Tests/extensions/ArrayPoolExtensionsTests.cs
Normal file
@ -0,0 +1,194 @@
|
||||
using System.Buffers;
|
||||
using GFramework.Core.extensions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 测试 ArrayPoolExtensions 的功能
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class ArrayPoolExtensionsTests
|
||||
{
|
||||
private ArrayPool<int> _pool = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_pool = ArrayPool<int>.Shared;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RentArray_Should_Return_Array()
|
||||
{
|
||||
// Act
|
||||
var array = _pool.RentArray(100);
|
||||
|
||||
// Assert
|
||||
Assert.That(array, Is.Not.Null);
|
||||
Assert.That(array.Length, Is.GreaterThanOrEqualTo(100));
|
||||
|
||||
// Cleanup
|
||||
_pool.ReturnArray(array);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReturnArray_Should_Not_Throw()
|
||||
{
|
||||
// Arrange
|
||||
var array = _pool.RentArray(100);
|
||||
|
||||
// Act & Assert
|
||||
Assert.DoesNotThrow(() => _pool.ReturnArray(array));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReturnArray_With_Clear_Should_Clear_Array()
|
||||
{
|
||||
// Arrange
|
||||
var array = _pool.RentArray(10);
|
||||
array[0] = 42;
|
||||
array[5] = 99;
|
||||
|
||||
// Act
|
||||
_pool.ReturnArray(array, clearArray: true);
|
||||
|
||||
// Assert
|
||||
Assert.That(array[0], Is.EqualTo(0));
|
||||
Assert.That(array[5], Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetScoped_Should_Return_Disposable_Wrapper()
|
||||
{
|
||||
// Act
|
||||
using var scoped = _pool.GetScoped(100);
|
||||
|
||||
// Assert
|
||||
Assert.That(scoped.Array, Is.Not.Null);
|
||||
Assert.That(scoped.Array.Length, Is.GreaterThanOrEqualTo(100));
|
||||
Assert.That(scoped.Length, Is.GreaterThanOrEqualTo(100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetScoped_Should_Auto_Return_On_Dispose()
|
||||
{
|
||||
// Arrange & Act
|
||||
int[] array;
|
||||
using (var scoped = _pool.GetScoped(100))
|
||||
{
|
||||
array = scoped.Array;
|
||||
array[0] = 42;
|
||||
}
|
||||
|
||||
// Assert - 如果没有异常就说明正常归还了
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScopedArray_AsSpan_Should_Return_Span()
|
||||
{
|
||||
// Arrange
|
||||
using var scoped = _pool.GetScoped(100);
|
||||
|
||||
// Act
|
||||
var span = scoped.AsSpan();
|
||||
|
||||
// Assert
|
||||
Assert.That(span.Length, Is.EqualTo(scoped.Length));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScopedArray_AsSpan_With_Range_Should_Return_Slice()
|
||||
{
|
||||
// Arrange
|
||||
using var scoped = _pool.GetScoped(100);
|
||||
|
||||
// Act
|
||||
var span = scoped.AsSpan(10, 20);
|
||||
|
||||
// Assert
|
||||
Assert.That(span.Length, Is.EqualTo(20));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetScoped_With_ClearOnReturn_Should_Clear_Array()
|
||||
{
|
||||
// Arrange
|
||||
int[] array;
|
||||
using (var scoped = _pool.GetScoped(10, clearOnReturn: true))
|
||||
{
|
||||
array = scoped.Array;
|
||||
array[0] = 42;
|
||||
array[5] = 99;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.That(array[0], Is.EqualTo(0));
|
||||
Assert.That(array[5], Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Multiple_Scoped_Arrays_Should_Work_Independently()
|
||||
{
|
||||
// Act
|
||||
using var scoped1 = _pool.GetScoped(50);
|
||||
using var scoped2 = _pool.GetScoped(100);
|
||||
|
||||
scoped1.Array[0] = 1;
|
||||
scoped2.Array[0] = 2;
|
||||
|
||||
// Assert
|
||||
Assert.That(scoped1.Array[0], Is.EqualTo(1));
|
||||
Assert.That(scoped2.Array[0], Is.EqualTo(2));
|
||||
Assert.That(scoped1.Array, Is.Not.SameAs(scoped2.Array));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RentArray_Should_Be_Reusable()
|
||||
{
|
||||
// Arrange
|
||||
var array1 = _pool.RentArray(100);
|
||||
array1[0] = 42;
|
||||
_pool.ReturnArray(array1, clearArray: true);
|
||||
|
||||
// Act
|
||||
var array2 = _pool.RentArray(100);
|
||||
|
||||
// Assert
|
||||
// 可能是同一个数组(已清空),也可能是不同的数组
|
||||
Assert.That(array2, Is.Not.Null);
|
||||
Assert.That(array2.Length, Is.GreaterThanOrEqualTo(100));
|
||||
|
||||
// Cleanup
|
||||
_pool.ReturnArray(array2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScopedArray_Should_Work_With_Using_Declaration()
|
||||
{
|
||||
// Act
|
||||
using var scoped = _pool.GetScoped(50);
|
||||
scoped.Array[0] = 123;
|
||||
|
||||
// Assert
|
||||
Assert.That(scoped.Array[0], Is.EqualTo(123));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AsSpan_Should_Allow_Span_Operations()
|
||||
{
|
||||
// Arrange
|
||||
using var scoped = _pool.GetScoped(10);
|
||||
var span = scoped.AsSpan();
|
||||
|
||||
// Act
|
||||
span.Fill(42);
|
||||
|
||||
// Assert
|
||||
for (var i = 0; i < span.Length; i++)
|
||||
{
|
||||
Assert.That(span[i], Is.EqualTo(42));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,6 +119,137 @@ public class ObjectPoolTests
|
||||
var obj2 = _pool.Acquire("test");
|
||||
Assert.That(obj2.OnAcquireCalled, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证GetPoolSize应该返回正确的池大小
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GetPoolSize_Should_Return_Correct_Size()
|
||||
{
|
||||
// Arrange
|
||||
var obj1 = _pool.Acquire("test");
|
||||
var obj2 = _pool.Acquire("test");
|
||||
|
||||
// Act & Assert
|
||||
Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(0));
|
||||
|
||||
_pool.Release("test", obj1);
|
||||
Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(1));
|
||||
|
||||
_pool.Release("test", obj2);
|
||||
Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证GetActiveCount应该返回正确的活跃对象数量
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GetActiveCount_Should_Return_Correct_Count()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(0));
|
||||
|
||||
var obj1 = _pool.Acquire("test");
|
||||
Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(1));
|
||||
|
||||
var obj2 = _pool.Acquire("test");
|
||||
Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(2));
|
||||
|
||||
_pool.Release("test", obj1);
|
||||
Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(1));
|
||||
|
||||
_pool.Release("test", obj2);
|
||||
Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证SetMaxCapacity应该限制池的最大容量
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SetMaxCapacity_Should_Limit_Pool_Size()
|
||||
{
|
||||
// Arrange
|
||||
_pool.SetMaxCapacity("test", 2);
|
||||
var obj1 = _pool.Acquire("test");
|
||||
var obj2 = _pool.Acquire("test");
|
||||
var obj3 = _pool.Acquire("test");
|
||||
|
||||
// Act
|
||||
_pool.Release("test", obj1);
|
||||
_pool.Release("test", obj2);
|
||||
_pool.Release("test", obj3);
|
||||
|
||||
// Assert
|
||||
Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(2));
|
||||
Assert.That(obj3.OnPoolDestroyCalled, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证Prewarm应该预创建指定数量的对象
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Prewarm_Should_Create_Objects_In_Advance()
|
||||
{
|
||||
// Act
|
||||
_pool.Prewarm("test", 5);
|
||||
|
||||
// Assert
|
||||
Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(5));
|
||||
|
||||
var obj = _pool.Acquire("test");
|
||||
Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(4));
|
||||
Assert.That(obj.OnReleaseCalled, Is.True); // 预热时调用了OnRelease
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证GetStatistics应该返回正确的统计信息
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GetStatistics_Should_Return_Correct_Statistics()
|
||||
{
|
||||
// Arrange
|
||||
_pool.SetMaxCapacity("test", 2);
|
||||
_pool.Prewarm("test", 2);
|
||||
|
||||
var obj1 = _pool.Acquire("test");
|
||||
var obj2 = _pool.Acquire("test");
|
||||
var obj3 = _pool.Acquire("test"); // 这个会创建新对象
|
||||
|
||||
_pool.Release("test", obj1);
|
||||
_pool.Release("test", obj2);
|
||||
_pool.Release("test", obj3); // 这个会被销毁
|
||||
|
||||
// Act
|
||||
var stats = _pool.GetStatistics("test");
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.AvailableCount, Is.EqualTo(2));
|
||||
Assert.That(stats.ActiveCount, Is.EqualTo(0));
|
||||
Assert.That(stats.MaxCapacity, Is.EqualTo(2));
|
||||
Assert.That(stats.TotalCreated, Is.EqualTo(3)); // 2个预热 + 1个新创建
|
||||
Assert.That(stats.TotalAcquired, Is.EqualTo(3));
|
||||
Assert.That(stats.TotalReleased, Is.EqualTo(3));
|
||||
Assert.That(stats.TotalDestroyed, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证不存在的池应该返回空统计信息
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void GetStatistics_Should_Return_Empty_For_Nonexistent_Pool()
|
||||
{
|
||||
// Act
|
||||
var stats = _pool.GetStatistics("nonexistent");
|
||||
|
||||
// Assert
|
||||
Assert.That(stats.AvailableCount, Is.EqualTo(0));
|
||||
Assert.That(stats.ActiveCount, Is.EqualTo(0));
|
||||
Assert.That(stats.MaxCapacity, Is.EqualTo(0));
|
||||
Assert.That(stats.TotalCreated, Is.EqualTo(0));
|
||||
Assert.That(stats.TotalAcquired, Is.EqualTo(0));
|
||||
Assert.That(stats.TotalReleased, Is.EqualTo(0));
|
||||
Assert.That(stats.TotalDestroyed, Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
116
GFramework.Core.Tests/pool/StringBuilderPoolTests.cs
Normal file
116
GFramework.Core.Tests/pool/StringBuilderPoolTests.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using GFramework.Core.pool;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.pool;
|
||||
|
||||
/// <summary>
|
||||
/// 测试 StringBuilderPool 的功能
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class StringBuilderPoolTests
|
||||
{
|
||||
[Test]
|
||||
public void Rent_Should_Return_StringBuilder()
|
||||
{
|
||||
// Act
|
||||
var sb = StringBuilderPool.Rent();
|
||||
|
||||
// Assert
|
||||
Assert.That(sb, Is.Not.Null);
|
||||
Assert.That(sb.Length, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Rent_Should_Respect_Capacity()
|
||||
{
|
||||
// Act
|
||||
var sb = StringBuilderPool.Rent(512);
|
||||
|
||||
// Assert
|
||||
Assert.That(sb.Capacity, Is.GreaterThanOrEqualTo(512));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Return_Should_Clear_StringBuilder()
|
||||
{
|
||||
// Arrange
|
||||
var sb = StringBuilderPool.Rent();
|
||||
sb.Append("Hello World");
|
||||
|
||||
// Act
|
||||
StringBuilderPool.Return(sb);
|
||||
|
||||
// Assert
|
||||
Assert.That(sb.Length, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Return_Should_Not_Throw_For_Large_Capacity()
|
||||
{
|
||||
// Arrange
|
||||
var sb = StringBuilderPool.Rent(10000);
|
||||
|
||||
// Act & Assert
|
||||
Assert.DoesNotThrow(() => StringBuilderPool.Return(sb));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetScoped_Should_Return_Disposable_Wrapper()
|
||||
{
|
||||
// Act
|
||||
using var scoped = StringBuilderPool.GetScoped();
|
||||
|
||||
// Assert
|
||||
Assert.That(scoped.Value, Is.Not.Null);
|
||||
Assert.That(scoped.Value.Length, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetScoped_Should_Auto_Return_On_Dispose()
|
||||
{
|
||||
// Arrange
|
||||
var sb = StringBuilderPool.GetScoped().Value;
|
||||
sb.Append("Test");
|
||||
|
||||
// Act
|
||||
using (var scoped = StringBuilderPool.GetScoped())
|
||||
{
|
||||
scoped.Value.Append("Hello");
|
||||
}
|
||||
|
||||
// Assert - 如果没有异常就说明正常归还了
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StringBuilder_Should_Be_Reusable()
|
||||
{
|
||||
// Arrange
|
||||
var sb = StringBuilderPool.Rent();
|
||||
sb.Append("First use");
|
||||
StringBuilderPool.Return(sb);
|
||||
|
||||
// Act
|
||||
sb.Append("Second use");
|
||||
|
||||
// Assert
|
||||
Assert.That(sb.ToString(), Is.EqualTo("Second use"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetScoped_With_Using_Should_Work()
|
||||
{
|
||||
// Act
|
||||
string result;
|
||||
using (var scoped = StringBuilderPool.GetScoped())
|
||||
{
|
||||
scoped.Value.Append("Hello");
|
||||
scoped.Value.Append(" ");
|
||||
scoped.Value.Append("World");
|
||||
result = scoped.Value.ToString();
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo("Hello World"));
|
||||
}
|
||||
}
|
||||
136
GFramework.Core/extensions/ArrayPoolExtensions.cs
Normal file
136
GFramework.Core/extensions/ArrayPoolExtensions.cs
Normal file
@ -0,0 +1,136 @@
|
||||
using System.Buffers;
|
||||
|
||||
namespace GFramework.Core.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// ArrayPool 扩展方法,提供更便捷的数组池操作
|
||||
/// </summary>
|
||||
public static class ArrayPoolExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 从数组池中租用数组
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="pool">数组池实例</param>
|
||||
/// <param name="minimumLength">最小长度</param>
|
||||
/// <returns>租用的数组</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var pool = ArrayPool<int>.Shared;
|
||||
/// var array = pool.RentArray(100);
|
||||
/// try
|
||||
/// {
|
||||
/// // 使用数组
|
||||
/// }
|
||||
/// finally
|
||||
/// {
|
||||
/// pool.ReturnArray(array);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static T[] RentArray<T>(this ArrayPool<T> pool, int minimumLength)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pool);
|
||||
return pool.Rent(minimumLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数组归还到数组池
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="pool">数组池实例</param>
|
||||
/// <param name="array">要归还的数组</param>
|
||||
/// <param name="clearArray">是否清空数组内容</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var pool = ArrayPool<int>.Shared;
|
||||
/// var array = pool.RentArray(100);
|
||||
/// try
|
||||
/// {
|
||||
/// // 使用数组
|
||||
/// }
|
||||
/// finally
|
||||
/// {
|
||||
/// pool.ReturnArray(array, clearArray: true);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static void ReturnArray<T>(this ArrayPool<T> pool, T[] array, bool clearArray = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pool);
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
pool.Return(array, clearArray);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个作用域数组,使用完后自动归还
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
/// <param name="pool">数组池实例</param>
|
||||
/// <param name="minimumLength">最小长度</param>
|
||||
/// <param name="clearOnReturn">归还时是否清空数组</param>
|
||||
/// <returns>可自动释放的数组包装器</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var pool = ArrayPool<int>.Shared;
|
||||
/// using var scopedArray = pool.GetScoped(100);
|
||||
/// var array = scopedArray.Array;
|
||||
/// // 使用数组
|
||||
/// // 自动归还
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static ScopedArray<T> GetScoped<T>(this ArrayPool<T> pool, int minimumLength, bool clearOnReturn = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pool);
|
||||
return new ScopedArray<T>(pool, minimumLength, clearOnReturn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可自动释放的数组包装器
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数组元素类型</typeparam>
|
||||
public readonly struct ScopedArray<T> : IDisposable
|
||||
{
|
||||
private readonly ArrayPool<T> _pool;
|
||||
private readonly bool _clearOnReturn;
|
||||
|
||||
/// <summary>
|
||||
/// 获取租用的数组
|
||||
/// </summary>
|
||||
public T[] Array { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取数组的长度
|
||||
/// </summary>
|
||||
public int Length => Array.Length;
|
||||
|
||||
internal ScopedArray(ArrayPool<T> pool, int minimumLength, bool clearOnReturn)
|
||||
{
|
||||
_pool = pool;
|
||||
_clearOnReturn = clearOnReturn;
|
||||
Array = pool.Rent(minimumLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放数组并归还到池中
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_pool.Return(Array, _clearOnReturn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数组的 Span 视图
|
||||
/// </summary>
|
||||
/// <returns>数组的 Span</returns>
|
||||
public Span<T> AsSpan() => Array.AsSpan();
|
||||
|
||||
/// <summary>
|
||||
/// 获取数组指定范围的 Span 视图
|
||||
/// </summary>
|
||||
/// <param name="start">起始索引</param>
|
||||
/// <param name="length">长度</param>
|
||||
/// <returns>数组指定范围的 Span</returns>
|
||||
public Span<T> AsSpan(int start, int length) => Array.AsSpan(start, length);
|
||||
}
|
||||
}
|
||||
@ -12,9 +12,52 @@ public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
: AbstractSystem, IObjectPoolSystem<TKey, TObject> where TObject : IPoolableObject where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储对象池的字典,键为池标识,值为对应类型的对象栈
|
||||
/// 池信息类,用于管理对象池的核心数据结构和统计信息。
|
||||
/// 包含对象栈、容量限制以及各类操作的统计计数。
|
||||
/// </summary>
|
||||
protected readonly Dictionary<TKey, Stack<TObject>> Pools = new();
|
||||
protected class PoolInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 对象栈,用于存储可复用的对象实例。
|
||||
/// </summary>
|
||||
public Stack<TObject> Stack { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 池的最大容量限制,超过此数量时将不再创建新对象。
|
||||
/// </summary>
|
||||
public int MaxCapacity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共创建的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalCreated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共从池中获取的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalAcquired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共归还到池中的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalReleased { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共销毁的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalDestroyed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前活跃(正在使用)的对象数量统计。
|
||||
/// </summary>
|
||||
public int ActiveCount { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 存储对象池的字典,键为池标识,值为池信息
|
||||
/// </summary>
|
||||
protected readonly Dictionary<TKey, PoolInfo> Pools = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取对象池中的对象,如果池中没有可用对象则创建新的对象
|
||||
@ -23,16 +66,25 @@ public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
/// <returns>获取到的对象实例</returns>
|
||||
public TObject Acquire(TKey key)
|
||||
{
|
||||
if (!Pools.TryGetValue(key, out var pool))
|
||||
if (!Pools.TryGetValue(key, out var poolInfo))
|
||||
{
|
||||
pool = new Stack<TObject>();
|
||||
Pools[key] = pool;
|
||||
poolInfo = new PoolInfo();
|
||||
Pools[key] = poolInfo;
|
||||
}
|
||||
|
||||
var obj = pool.Count > 0
|
||||
? pool.Pop()
|
||||
: Create(key);
|
||||
TObject obj;
|
||||
if (poolInfo.Stack.Count > 0)
|
||||
{
|
||||
obj = poolInfo.Stack.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = Create(key);
|
||||
poolInfo.TotalCreated++;
|
||||
}
|
||||
|
||||
poolInfo.TotalAcquired++;
|
||||
poolInfo.ActiveCount++;
|
||||
obj.OnAcquire();
|
||||
return obj;
|
||||
}
|
||||
@ -46,13 +98,26 @@ public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
{
|
||||
obj.OnRelease();
|
||||
|
||||
if (!Pools.TryGetValue(key, out var pool))
|
||||
if (!Pools.TryGetValue(key, out var poolInfo))
|
||||
{
|
||||
pool = new Stack<TObject>();
|
||||
Pools[key] = pool;
|
||||
poolInfo = new PoolInfo();
|
||||
Pools[key] = poolInfo;
|
||||
}
|
||||
|
||||
pool.Push(obj);
|
||||
poolInfo.TotalReleased++;
|
||||
poolInfo.ActiveCount--;
|
||||
|
||||
// 检查容量限制
|
||||
if (poolInfo.MaxCapacity > 0 && poolInfo.Stack.Count >= poolInfo.MaxCapacity)
|
||||
{
|
||||
// 超过容量限制,销毁对象
|
||||
obj.OnPoolDestroy();
|
||||
poolInfo.TotalDestroyed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
poolInfo.Stack.Push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -61,11 +126,108 @@ public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
public void Clear()
|
||||
{
|
||||
// 遍历所有对象池,调用每个对象的销毁方法
|
||||
foreach (var obj in Pools.Values.SelectMany(pool => pool)) obj.OnPoolDestroy();
|
||||
foreach (var poolInfo in Pools.Values)
|
||||
{
|
||||
foreach (var obj in poolInfo.Stack)
|
||||
{
|
||||
obj.OnPoolDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
Pools.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定池的当前大小
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <returns>池中可用对象的数量</returns>
|
||||
public int GetPoolSize(TKey key)
|
||||
{
|
||||
return Pools.TryGetValue(key, out var poolInfo) ? poolInfo.Stack.Count : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定池的活跃对象数量
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <returns>已被获取但未释放的对象数量</returns>
|
||||
public int GetActiveCount(TKey key)
|
||||
{
|
||||
return Pools.TryGetValue(key, out var poolInfo) ? poolInfo.ActiveCount : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定池的最大容量
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <param name="maxCapacity">最大容量,超过此容量的对象将被销毁而不是放回池中</param>
|
||||
public void SetMaxCapacity(TKey key, int maxCapacity)
|
||||
{
|
||||
if (!Pools.TryGetValue(key, out var poolInfo))
|
||||
{
|
||||
poolInfo = new PoolInfo();
|
||||
Pools[key] = poolInfo;
|
||||
}
|
||||
|
||||
poolInfo.MaxCapacity = maxCapacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预热对象池,提前创建指定数量的对象
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <param name="count">要预创建的对象数量</param>
|
||||
public void Prewarm(TKey key, int count)
|
||||
{
|
||||
if (!Pools.TryGetValue(key, out var poolInfo))
|
||||
{
|
||||
poolInfo = new PoolInfo();
|
||||
Pools[key] = poolInfo;
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var obj = Create(key);
|
||||
poolInfo.TotalCreated++;
|
||||
obj.OnRelease();
|
||||
poolInfo.Stack.Push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定池的统计信息
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <returns>池的统计信息</returns>
|
||||
public PoolStatistics GetStatistics(TKey key)
|
||||
{
|
||||
if (!Pools.TryGetValue(key, out var poolInfo))
|
||||
{
|
||||
return new PoolStatistics
|
||||
{
|
||||
AvailableCount = 0,
|
||||
ActiveCount = 0,
|
||||
MaxCapacity = 0,
|
||||
TotalCreated = 0,
|
||||
TotalAcquired = 0,
|
||||
TotalReleased = 0,
|
||||
TotalDestroyed = 0
|
||||
};
|
||||
}
|
||||
|
||||
return new PoolStatistics
|
||||
{
|
||||
AvailableCount = poolInfo.Stack.Count,
|
||||
ActiveCount = poolInfo.ActiveCount,
|
||||
MaxCapacity = poolInfo.MaxCapacity,
|
||||
TotalCreated = poolInfo.TotalCreated,
|
||||
TotalAcquired = poolInfo.TotalAcquired,
|
||||
TotalReleased = poolInfo.TotalReleased,
|
||||
TotalDestroyed = poolInfo.TotalDestroyed
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的对象实例(由子类决定怎么创建)
|
||||
/// </summary>
|
||||
|
||||
111
GFramework.Core/pool/StringBuilderPool.cs
Normal file
111
GFramework.Core/pool/StringBuilderPool.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using System.Text;
|
||||
|
||||
namespace GFramework.Core.pool;
|
||||
|
||||
/// <summary>
|
||||
/// StringBuilder 对象池,提供高性能的字符串构建器复用
|
||||
/// </summary>
|
||||
public static class StringBuilderPool
|
||||
{
|
||||
private const int DefaultCapacity = 256;
|
||||
private const int MaxRetainedCapacity = 4096;
|
||||
|
||||
/// <summary>
|
||||
/// 从池中租用一个 StringBuilder
|
||||
/// </summary>
|
||||
/// <param name="capacity">初始容量,默认为 256</param>
|
||||
/// <returns>StringBuilder 实例</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var sb = StringBuilderPool.Rent();
|
||||
/// try
|
||||
/// {
|
||||
/// sb.Append("Hello");
|
||||
/// sb.Append(" World");
|
||||
/// return sb.ToString();
|
||||
/// }
|
||||
/// finally
|
||||
/// {
|
||||
/// StringBuilderPool.Return(sb);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static StringBuilder Rent(int capacity = DefaultCapacity)
|
||||
{
|
||||
var sb = new StringBuilder(capacity);
|
||||
return sb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 StringBuilder 归还到池中
|
||||
/// </summary>
|
||||
/// <param name="builder">要归还的 StringBuilder</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var sb = StringBuilderPool.Rent();
|
||||
/// try
|
||||
/// {
|
||||
/// sb.Append("Hello World");
|
||||
/// Console.WriteLine(sb.ToString());
|
||||
/// }
|
||||
/// finally
|
||||
/// {
|
||||
/// StringBuilderPool.Return(sb);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static void Return(StringBuilder builder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
// 如果容量过大,不放回池中
|
||||
if (builder.Capacity > MaxRetainedCapacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builder.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个 StringBuilder,使用完后自动归还
|
||||
/// </summary>
|
||||
/// <param name="capacity">初始容量</param>
|
||||
/// <returns>可自动释放的 StringBuilder 包装器</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// using var sb = StringBuilderPool.GetScoped();
|
||||
/// sb.Value.Append("Hello");
|
||||
/// sb.Value.Append(" World");
|
||||
/// return sb.Value.ToString();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static ScopedStringBuilder GetScoped(int capacity = DefaultCapacity)
|
||||
{
|
||||
return new ScopedStringBuilder(Rent(capacity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可自动释放的 StringBuilder 包装器
|
||||
/// </summary>
|
||||
public readonly struct ScopedStringBuilder : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 StringBuilder 实例
|
||||
/// </summary>
|
||||
public StringBuilder Value { get; }
|
||||
|
||||
internal ScopedStringBuilder(StringBuilder value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放 StringBuilder 并归还到池中
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Return(Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user