ObjectPool 基本用法:从创建到归还
直接用
ObjectPool<t></t>需要先配置一个池策略,不能 new 出来就用。最简路径是借助
DefaultObjectPoolProvider创建带默认策略的池:
var provider = new DefaultObjectPoolProvider(); var pool = provider.Create(new DefaultPooledObjectPolicy<StringBuilder>());
获取对象用
Get(),用完必须调用
Return()—— 否则对象不会回到池里,等于白配:
var sb = pool.Get();→ 拿到实例(可能新建,也可能复用)
sb.Append("hello"); → 正常使用
pool.Return(sb);→ 关键!不调用这句,下次
Get()不会拿到它
自定义 PooledObjectPolicy:控制创建、验证和清理逻辑
默认策略只做
new T()和
obj?.Clear()(仅对
StringBuilder等少数类型生效)。多数场景需要自己写策略,比如复用
MemoryStream时得重置位置和长度:
public class MemoryStreamPolicy : IPooledObjectPolicy<MemoryStream>
{
public MemoryStream Create() => new MemoryStream(1024);
public bool Return(MemoryStream obj) => obj.Length == 0; // 可选校验
public void Return(MemoryStream obj)
{
obj.Position = 0;
obj.SetLength(0); // 清空内容,但保留缓冲区
}
}
注意两点:
Return()方法返回
bool表示是否允许归还;返回
false会被直接丢弃(如对象状态异常) 不要在
Return()里调用
Dispose()—— 池不负责释放资源,除非你明确在策略里处理
性能陷阱:池大小没限制,内存可能越攒越多
DefaultObjectPoolProvider默认不限制池容量,空闲对象一直留着。高并发下若对象体积大(比如含大数组的类),容易吃光内存。
解决方法是传入
PoolingPolicyOptions控制上限:
var options = new PoolingPolicyOptions
{
MaximumRetained = 50, // 最多缓存 50 个空闲实例
};
var pool = provider.Create(new MyPolicy(), options);
另外,
MaximumRetained是 per-pool 的,不是全局总数。每个
Create()调用都独立计数。
常见错误:把池当成单例或线程局部存储用
有人误以为池本身是线程安全的「容器」,然后在多个线程间共享同一个
ObjectPool<t></t>实例 —— 这没问题,
ObjectPool<t></t>内部是线程安全的。
但错在:把池和它管理的对象混为一谈。典型错误包括:
在线程 A 中Get()后,把对象传给线程 B 使用,再在线程 B 中
Return()—— 可以,但需确保对象本身线程安全(如
StringBuilder不是) 重复
Return()同一个对象两次 —— 会触发内部断言失败或静默忽略,取决于策略实现 在
async方法中
await后才
Return(),中间被其他代码误用该对象
最稳妥的做法:Get 和 Return 尽量写在同一个作用域内,用
using无法自动 Return,必须显式写。
