我有一个场景,当我需要在某个内部函数(一个服务)中操作大数组时,但这个操作的结果是被父函数消耗(序列化为JSON并通过HTTP返回):

public IActionResult ParentFunction()
{
    var returnedArray = InnerFunction(1000);
    return Ok(returnedArray.Take(1000));
}

public int[] InnerFunction(int count)
{
    var rentedArray = _defaultArrayPool.Rent(count);
    // make operation on rentedArray
    return rentedArray;
}

在上面的代码中,数组显然没有返回到_defaultArrayPool,因此它永远不会被重用.

我考虑了几个选项,但我想知道什么是最好的实现?

Option 1 - Return by parent function

I don't like this option because Rent and Return are called in different parts of the code.

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);
    // make operation on rentedArray
    return rentedArray;
}

Option 2 - Rent and Return by parent function, and pass as reference

It is better, but won't work if ParentFunction does not know the length/count upfront.

public IActionResult ParentFunction()
{
    var rentedArray = _defaultArrayPool.Rent(1000); // will not work if 'Count' is unknown here, and is to be determined by InnerFunction
    try
    {
        InnerFunction(rentedArray, 1000);
        return Ok(rentedArray.Take(1000));
    }
    finally
    {
        if (rentedArray != null)
        {
            _defaultArrayPool.Return(rentedArray);
        }
    }
}

public void InnerFunction(int[] arr, int count)
{
    // make operation on arr
}

Option 3 - Rent and Return by different functions

It will work when inner function is determining needed count/length

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; // determin lenght of the array
    arr = _defaultArrayPool.Rent(count);
    // make operation on arr
    return count;
}

还有其他更好的 Select 吗?

推荐答案

我将使用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.第一次见面

Csharp相关问答推荐

为什么xslWriter不总是按照xslWriterSet中指定的格式格式化该文档?

下拉图片点击MudBlazor

有没有办法把+02:00转换成TimeSpan?""

如何使用XmlSerializer反序列化字符串数组?

AsNoTrackingWithIdentitySolutions()似乎不起作用?

.NET HttpClient、JsonSerializer或误用的Stream中的内存泄漏?

mocking对象的引发事件并作为用于调用方法的参数对象传递到那里

实体框架核心中的ComplexType和OwnsOne有什么不同?

由于POST中的应用程序/JWT,出现不支持的内容类型异常

持有者安全定义未显示在Swagger.NET 8中

正在try 从Blazor中的API读取JSON

正在寻找新的.NET8 Blazor Web应用程序.如何将.js添加到.razor页面?

如何从另一个类的列表中按ID取值

C#Microsoft.CodeAnalysis.CSharp.Scriiting不等待并行.对于

在.NET Maui中,Flyoutindow/Hamburger菜单可以在shell 之外实现吗?

如何使用LINQ在C#中填充列表列表?

如何保存具有多个重叠图片框的图片框?

如果图表S批注包含使用LINQ的具有特定名称的批注,我如何签入C#

测试单个对象是否与Func<;T,bool>;匹配

无法创建&Quot;&Quot;类型的实例,因为无法将一个或多个参数绑定到