diff --git a/GFramework.Core.Abstractions/pool/IObjectPoolSystem.cs b/GFramework.Core.Abstractions/pool/IObjectPoolSystem.cs index 4f73a78..62c3631 100644 --- a/GFramework.Core.Abstractions/pool/IObjectPoolSystem.cs +++ b/GFramework.Core.Abstractions/pool/IObjectPoolSystem.cs @@ -46,7 +46,7 @@ public interface IObjectPoolSystem /// 设置指定池的最大容量 /// /// 对象池的键 - /// 最大容量,超过此容量的对象将被销毁而不是放回池中 + /// 池中保留的最大对象数量。超过此数量时,释放的对象将被销毁而不是放回池中。设置为 0 表示无限制。 void SetMaxCapacity(TKey key, int maxCapacity); /// diff --git a/GFramework.Core.Tests/pool/ObjectPoolTests.cs b/GFramework.Core.Tests/pool/ObjectPoolTests.cs index 0ce50d9..f07bc0c 100644 --- a/GFramework.Core.Tests/pool/ObjectPoolTests.cs +++ b/GFramework.Core.Tests/pool/ObjectPoolTests.cs @@ -250,6 +250,40 @@ public class ObjectPoolTests Assert.That(stats.TotalReleased, Is.EqualTo(0)); Assert.That(stats.TotalDestroyed, Is.EqualTo(0)); } + + /// + /// 验证双重释放不会导致 ActiveCount 变为负数 + /// + [Test] + public void Release_Should_Not_Make_ActiveCount_Negative_On_Double_Release() + { + // Arrange + var obj = _pool.Acquire("test"); + _pool.Release("test", obj); + + // Act - 双重释放 + _pool.Release("test", obj); + + // Assert + Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(0)); + } + + /// + /// 验证使用错误的 key 释放不会影响原 key 的 ActiveCount + /// + [Test] + public void Release_With_Wrong_Key_Should_Not_Affect_Original_Key_ActiveCount() + { + // Arrange + var obj = _pool.Acquire("key1"); + + // Act - 使用错误的 key 释放 + _pool.Release("key2", obj); + + // Assert + Assert.That(_pool.GetActiveCount("key1"), Is.EqualTo(1)); + Assert.That(_pool.GetActiveCount("key2"), Is.EqualTo(0)); + } } /// diff --git a/GFramework.Core.Tests/pool/StringBuilderPoolTests.cs b/GFramework.Core.Tests/pool/StringBuilderPoolTests.cs index 66cde30..0c65bc2 100644 --- a/GFramework.Core.Tests/pool/StringBuilderPoolTests.cs +++ b/GFramework.Core.Tests/pool/StringBuilderPoolTests.cs @@ -113,4 +113,85 @@ public class StringBuilderPoolTests // Assert Assert.That(result, Is.EqualTo("Hello World")); } -} + + /// + /// 验证 Rent 方法会复用已归还的 StringBuilder 实例 + /// + [Test] + public void Rent_Should_Reuse_Returned_StringBuilder() + { + // Arrange + var sb1 = StringBuilderPool.Rent(); + var originalInstance = sb1; + StringBuilderPool.Return(sb1); + + // Act + var sb2 = StringBuilderPool.Rent(); + + // Assert + Assert.That(sb2, Is.SameAs(originalInstance), "应该复用同一个实例"); + } + + /// + /// 验证 Rent 方法会确保返回的 StringBuilder 满足最小容量要求 + /// + [Test] + public void Rent_Should_Ensure_Minimum_Capacity() + { + // Arrange + var sb1 = StringBuilderPool.Rent(100); + StringBuilderPool.Return(sb1); + + // Act - 请求更大容量 + var sb2 = StringBuilderPool.Rent(500); + + // Assert + Assert.That(sb2.Capacity, Is.GreaterThanOrEqualTo(500)); + } + + /// + /// 验证超过最大保留容量的 StringBuilder 不会被池化 + /// + [Test] + public void Return_Should_Not_Pool_Large_Capacity_StringBuilder() + { + // Arrange + var sb1 = StringBuilderPool.Rent(10000); + StringBuilderPool.Return(sb1); + + // Act - 租用新的小容量 StringBuilder + var sb2 = StringBuilderPool.Rent(100); + + // Assert + Assert.That(sb2, Is.Not.SameAs(sb1), "大容量实例不应被池化"); + } + + /// + /// 验证对象池在多线程环境下的线程安全性 + /// + [Test] + public void Pool_Should_Be_Thread_Safe() + { + // Arrange + const int threadCount = 10; + const int operationsPerThread = 100; + var tasks = new Task[threadCount]; + + // Act + for (int i = 0; i < threadCount; i++) + { + tasks[i] = Task.Run(() => + { + for (int j = 0; j < operationsPerThread; j++) + { + var sb = StringBuilderPool.Rent(); + sb.Append("Test"); + StringBuilderPool.Return(sb); + } + }); + } + + // Assert + Assert.DoesNotThrow(() => Task.WaitAll(tasks)); + } +} \ No newline at end of file diff --git a/GFramework.Core/pool/AbstractObjectPoolSystem.cs b/GFramework.Core/pool/AbstractObjectPoolSystem.cs index 95d0b93..0f2ca87 100644 --- a/GFramework.Core/pool/AbstractObjectPoolSystem.cs +++ b/GFramework.Core/pool/AbstractObjectPoolSystem.cs @@ -1,4 +1,5 @@ -using GFramework.Core.Abstractions.pool; +using System.Diagnostics; +using GFramework.Core.Abstractions.pool; using GFramework.Core.system; namespace GFramework.Core.pool; @@ -11,49 +12,6 @@ namespace GFramework.Core.pool; public abstract class AbstractObjectPoolSystem : AbstractSystem, IObjectPoolSystem where TObject : IPoolableObject where TKey : notnull { - /// - /// 池信息类,用于管理对象池的核心数据结构和统计信息。 - /// 包含对象栈、容量限制以及各类操作的统计计数。 - /// - protected class PoolInfo - { - /// - /// 对象栈,用于存储可复用的对象实例。 - /// - public Stack Stack { get; } = new(); - - /// - /// 池的最大容量限制,超过此数量时将不再创建新对象。 - /// - public int MaxCapacity { get; set; } - - /// - /// 总共创建的对象数量统计。 - /// - public int TotalCreated { get; set; } - - /// - /// 总共从池中获取的对象数量统计。 - /// - public int TotalAcquired { get; set; } - - /// - /// 总共归还到池中的对象数量统计。 - /// - public int TotalReleased { get; set; } - - /// - /// 总共销毁的对象数量统计。 - /// - public int TotalDestroyed { get; set; } - - /// - /// 当前活跃(正在使用)的对象数量统计。 - /// - public int ActiveCount { get; set; } - } - - /// /// 存储对象池的字典,键为池标识,值为池信息 /// @@ -105,7 +63,19 @@ public abstract class AbstractObjectPoolSystem } poolInfo.TotalReleased++; - poolInfo.ActiveCount--; + + // 防止 ActiveCount 变为负数 + if (poolInfo.ActiveCount > 0) + { + poolInfo.ActiveCount--; + } + else + { + // 记录警告:检测到可能的双重释放或错误释放 + Debug.WriteLine( + $"[ObjectPool] Warning: Attempting to release object for key '{key}' " + + $"but ActiveCount is already 0. Possible double-release or incorrect key."); + } // 检查容量限制 if (poolInfo.MaxCapacity > 0 && poolInfo.Stack.Count >= poolInfo.MaxCapacity) @@ -161,7 +131,7 @@ public abstract class AbstractObjectPoolSystem /// 设置指定池的最大容量 /// /// 对象池的键 - /// 最大容量,超过此容量的对象将被销毁而不是放回池中 + /// 池中保留的最大对象数量。超过此数量时,释放的对象将被销毁而不是放回池中。设置为 0 表示无限制。 public void SetMaxCapacity(TKey key, int maxCapacity) { if (!Pools.TryGetValue(key, out var poolInfo)) @@ -242,4 +212,47 @@ public abstract class AbstractObjectPoolSystem { Clear(); } + + /// + /// 池信息类,用于管理对象池的核心数据结构和统计信息。 + /// 包含对象栈、容量限制以及各类操作的统计计数。 + /// + protected class PoolInfo + { + /// + /// 对象栈,用于存储可复用的对象实例。 + /// + public Stack Stack { get; } = new(); + + /// + /// 池中保留的最大对象数量。当释放对象时,如果池中对象数已达到此限制, + /// 对象将被销毁而不是放回池中。设置为 0 表示无限制。 + /// + public int MaxCapacity { get; set; } + + /// + /// 总共创建的对象数量统计。 + /// + public int TotalCreated { get; set; } + + /// + /// 总共从池中获取的对象数量统计。 + /// + public int TotalAcquired { get; set; } + + /// + /// 总共归还到池中的对象数量统计。 + /// + public int TotalReleased { get; set; } + + /// + /// 总共销毁的对象数量统计。 + /// + public int TotalDestroyed { get; set; } + + /// + /// 当前活跃(正在使用)的对象数量统计。 + /// + public int ActiveCount { get; set; } + } } \ No newline at end of file diff --git a/GFramework.Core/pool/StringBuilderPool.cs b/GFramework.Core/pool/StringBuilderPool.cs index ff49027..55cf512 100644 --- a/GFramework.Core/pool/StringBuilderPool.cs +++ b/GFramework.Core/pool/StringBuilderPool.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Text; namespace GFramework.Core.pool; @@ -10,11 +11,18 @@ public static class StringBuilderPool private const int DefaultCapacity = 256; private const int MaxRetainedCapacity = 4096; + // 使用 ConcurrentBag 实现线程安全的对象池 + private static readonly ConcurrentBag Pool = new(); + /// /// 从池中租用一个 StringBuilder /// /// 初始容量,默认为 256 /// StringBuilder 实例 + /// + /// 优先从池中获取可复用的实例,如果池为空则创建新实例。 + /// 使用完毕后应调用 方法归还到池中以便复用。 + /// /// /// /// var sb = StringBuilderPool.Rent(); @@ -32,14 +40,29 @@ public static class StringBuilderPool /// public static StringBuilder Rent(int capacity = DefaultCapacity) { - var sb = new StringBuilder(capacity); - return sb; + if (Pool.TryTake(out var sb)) + { + // 从池中获取到实例,确保容量满足需求 + if (sb.Capacity < capacity) + { + sb.Capacity = capacity; + } + + return sb; + } + + // 池为空,创建新实例 + return new StringBuilder(capacity); } /// /// 将 StringBuilder 归还到池中 /// /// 要归还的 StringBuilder + /// + /// 如果 StringBuilder 的容量超过 , + /// 则不会放回池中,而是直接丢弃以避免保留过大的对象。 + /// /// /// /// var sb = StringBuilderPool.Rent(); @@ -64,7 +87,9 @@ public static class StringBuilderPool return; } + // 清空内容并放回池中 builder.Clear(); + Pool.Add(builder); } /// @@ -108,4 +133,4 @@ public static class StringBuilderPool Return(Value); } } -} +} \ No newline at end of file