我将使用basic dispose pattern返回一些IDisposable
,这些IDisposable
封装租用的数组,并在释放时返回它.
首先定义以下一次性包装:
public sealed class RentedArrayWrapper<T> : IList<T>, IDisposable
{
public T [] array;
readonly ArrayPool<T>? pool;
readonly int count;
public static RentedArrayWrapper<T> Create(ArrayPool<T> pool, int count) => new RentedArrayWrapper<T>(pool.Rent(count), pool, count);
RentedArrayWrapper(T [] array, ArrayPool<T>? pool,int count)
{
if (count < 0 || count > array.Length)
throw new ArgumentException("count < 0 || count > array.Length");
this.array = array ?? throw new ArgumentNullException(nameof(array));
this.pool = pool;
this.count = count;
}
public T [] Array => array ?? throw new ObjectDisposedException(GetType().Name);
public Memory<T> Memory => Array.AsMemory().Slice(0, count);
public T this[int index]
{
get
{
if (index < 0 || index >= count)
throw new ArgumentOutOfRangeException();
return Array[index];
}
set
{
if (index < 0 || index >= count)
throw new ArgumentOutOfRangeException();
Array[index] = value;
}
}
public IEnumerable<T> EnumerateAndDispose()
{
IEnumerable<T> EnumerateAndDisposeInner()
{
try
{
foreach (var item in this)
yield return item;
}
finally
{
Dispose();
}
}
CheckDisposed();
return EnumerateAndDisposeInner();
}
public IEnumerator<T> GetEnumerator()
{
IEnumerator<T> GetEnumeratorInner()
{
CheckDisposed();
for (int i = 0; i < count; i++)
yield return this[i];
}
CheckDisposed();
return GetEnumeratorInner();
}
public int IndexOf(T item) => System.Array.IndexOf<T>(Array, item, 0, count);
public bool Contains(T item) => IndexOf(item) >= 0;
public void CopyTo(T[] array, int arrayIndex) => Memory.CopyTo(array.AsMemory().Slice(arrayIndex));
public int Count => count;
void IList<T>.Insert(int index, T item) => throw new NotImplementedException();
void IList<T>.RemoveAt(int index) => throw new NotImplementedException();
void ICollection<T>.Add(T item) => throw new NotImplementedException();
void ICollection<T>.Clear() => throw new NotImplementedException();
bool ICollection<T>.Remove(T item) => throw new NotImplementedException();
bool ICollection<T>.IsReadOnly => true; // Indicates items cannot be added or removed
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void CheckDisposed()
{
if (this.array == null)
throw new ObjectDisposedException(GetType().Name);
}
void Dispose(bool disposing)
{
if (disposing)
if (Interlocked.Exchange(ref this.array, null!) is {} array)
pool?.Return(array);
}
}
public static partial class ArrayPoolExtensions
{
public static RentedArrayWrapper<T> RentWrapper<T>(this ArrayPool<T> pool, int count) => RentedArrayWrapper<T>.Create(pool, count);
}
现在你可以按照如下方式编写你的父函数和内部函数:
public IActionResult ParentFunction()
{
using var wrapper = InnerFunction(1000);
// Take() uses deferred execution so we must materialize the rented array into a final non-disposable result so that
// ObObjectResult.ExecuteResultAsync(ActionContext context) does not attempt to serialize the rented array after it has been returned.
return Ok(wrapper.Take(1000).ToArray());
}
public RentedArrayWrapper<int> InnerFunction(int count)
{
var wrapper = _defaultArrayPool.RentWrapper(count);
// make operation on wrapper.Array
return wrapper;
}
here.第一次见面
也就是说,所有的实现都存在一个根本问题:在实际执行OkObjectResult
之前,您将租用的数组返回到数组池,并将值序列化到响应流中. 因此,如果在此期间,同一个数组池内存随后被租用到其他地方,则返回的内容很可能是随机的.
你有什么 Select 来解决这个问题? 我想到了几个 Select .
Firstly,您可以考虑返回一些可重写的包装器,在一次迭代后处理RentedArrayWrapper
,如下所示:
public IActionResult ParentFunction()
{
var wrapper = InnerFunction(1000);
return Ok(wrapper.EnumerateAndDispose());
}
public RentedArrayWrapper<int> InnerFunction(int count)
{
var wrapper = _defaultArrayPool.RentWrapper(count);
// make operation on wrapper.Array
return wrapper;
}
虽然这是可行的,但对我来说似乎是粗略的,因为我的一般感觉是,计数器不应该有side-effects. here.第一次见面
Secondly,你可以考虑将ControllerBase.Ok(object)
返回的类型OkObjectResult
子类化,让它在执行后处理它的值,如下所示:
public class OkDisposableResult : OkObjectResult
{
public OkDisposableResult(IDisposable disposable) : base(disposable) { }
public override async Task ExecuteResultAsync(ActionContext context)
{
try
{
await base.ExecuteResultAsync(context);
}
finally
{
if (Value is IDisposable disposable)
disposable.Dispose();
}
}
public override void ExecuteResult(ActionContext context)
{
// I'm not sure this is ever actually called
try
{
base.ExecuteResult(context);
}
finally
{
if (Value is IDisposable disposable)
disposable.Dispose();
}
}
}
然后返回租用的数组包装器如下:
public IActionResult ParentFunction()
{
var wrapper = InnerFunction(1000);
return new OkDisposableResult(wrapper);
}
public RentedArrayWrapper<int> InnerFunction(int count)
{
var wrapper = _defaultArrayPool.RentWrapper(count);
// make operation on wrapper.Array
return wrapper;
}
here.第一次见面