内部函数租用ArrayPool数组后,如何正确归还至数组池?
数组池复用的优化方案探讨
场景描述
需要在内部函数中操作大型数组,操作结果由父函数消费(序列化为JSON通过HTTP返回)。原代码存在数组未归还至_defaultArrayPool、无法复用的问题:
public IActionResult ParentFunction() { var returnedArray = InnerFunction(1000); return Ok(returnedArray.Take(1000)); } public int[] InnerFunction(int count) { var rentedArray = _defaultArrayPool.Rent(count); // 对rentedArray执行操作 return rentedArray; }
现有方案分析
方案1 - 父函数负责归还
缺点:Rent和Return调用分散在不同函数,易遗漏或出错
public IActionResult ParentFunction() { int[] returnedArray = null; try { returnedArray = InnerFunction(1000); return Ok(returnedArray.Take(1000)); } finally { if (returnedArray != null) { _defaultArrayPool.Return(returnedArray); } } } public int[] InnerFunction(int count) { var rentedArray = _defaultArrayPool.Rent(count); // 对rentedArray执行操作 return rentedArray; }
方案2 - 父函数租用并归还,数组按引用传递
优点:Rent/Return在同一函数;缺点:父函数必须预先知晓数组长度,无法适配内部函数动态决定长度的场景
public IActionResult ParentFunction() { var rentedArray = _defaultArrayPool.Rent(1000); // 若长度由内部函数决定则不可用 try { InnerFunction(rentedArray, 1000); return Ok(rentedArray.Take(1000)); } finally { if (rentedArray != null) { _defaultArrayPool.Return(rentedArray); } } } public void InnerFunction(int[] arr, int count) { // 对arr执行操作 }
方案3 - 内部函数租用,父函数归还
优点:支持内部函数动态决定数组长度;缺点:仍存在Rent/Return分散的问题
public IActionResult ParentFunction() { int[] rentedArray = null; try { var count = InnerFunction(out rentedArray); return Ok(rentedArray.Take(count)); } finally { if (rentedArray != null) { _defaultArrayPool.Return(rentedArray); } } } public int InnerFunction(out int[] arr) { int count = 1000; // 动态确定数组长度 arr = _defaultArrayPool.Rent(count); // 对arr执行操作 return count; }
更优实现方案
方案4:使用回调委托,封装Rent/Return逻辑
将数组的租用、操作、归还逻辑统一在内部函数中,父函数只需提供结果处理的回调,彻底避免Rent/Return分散:
public IActionResult ParentFunction() { IActionResult result = null; InnerFunction(1000, (array, count) => { result = Ok(array.Take(count)); }); return result; } public void InnerFunction(int count, Action<int[], int> processResult) { var rentedArray = _defaultArrayPool.Rent(count); try { // 对rentedArray执行操作 processResult(rentedArray, count); } finally { _defaultArrayPool.Return(rentedArray); } }
如果内部函数需要动态确定长度,只需调整参数:
public IActionResult ParentFunction() { IActionResult result = null; InnerFunction((array, count) => { result = Ok(array.Take(count)); }); return result; } public void InnerFunction(Action<int[], int> processResult) { int count = DetermineDynamicCount(); // 动态计算长度 var rentedArray = _defaultArrayPool.Rent(count); try { // 对rentedArray执行操作 processResult(rentedArray, count); } finally { _defaultArrayPool.Return(rentedArray); } } private int DetermineDynamicCount() { // 实际场景中动态计算所需长度 return 1000; }
方案5:自定义Disposable包装类,利用using自动归还
通过实现IDisposable接口,将数组的归还逻辑封装在Dispose方法中,借助C#的using语句自动释放,代码更简洁优雅:
public class PooledArray<T> : IDisposable { private readonly ArrayPool<T> _pool; public T[] Array { get; } public int Count { get; } public PooledArray(ArrayPool<T> pool, int count) { _pool = pool; Count = count; Array = pool.Rent(count); } public void Dispose() { _pool.Return(Array); } } // 固定长度调用示例 public IActionResult ParentFunction() { using var pooledArray = InnerFunction(1000); return Ok(pooledArray.Array.Take(pooledArray.Count)); } public PooledArray<int> InnerFunction(int count) { var pooled = new PooledArray<int>(_defaultArrayPool, count); // 对pooled.Array执行操作 return pooled; } // 动态长度调用示例 public IActionResult ParentFunctionDynamic() { using var pooledArray = InnerFunctionDynamic(); return Ok(pooledArray.Array.Take(pooledArray.Count)); } public PooledArray<int> InnerFunctionDynamic() { int count = DetermineDynamicCount(); var pooled = new PooledArray<int>(_defaultArrayPool, count); // 对pooled.Array执行操作 return pooled; }
方案对比与推荐
- 回调委托方案:无需额外类,逻辑紧凑,Rent/Return完全封装在内部函数中,父函数只需关注结果处理,适合大多数场景。
- Disposable包装类方案:借助
using语法糖自动处理归还,代码可读性和维护性更强,适合需要复用数组池包装逻辑的场景。
这两种方案都解决了原方案中Rent/Return分散的问题,同时支持动态数组长度的场景,是更优的实现方式。
内容的提问来源于stack exchange,提问作者Maciej Pszczolinski
相关产品推荐
相关产品推荐

