避免 Span<T>。ToArray() 在异步方法中?

Avoiding Span<T>.ToArray() in an async method?

提问人:aybe 提问时间:11/7/2023 最后编辑:aybe 更新时间:11/7/2023 访问量:74

问:

将一些代码升级为异步代码,并意识到不能在方法中使用。Span<T>async

调用可以解决此问题,但代价是每次都分配。Span<T>.ToArray()

您能就如何避免在这种情况下进行分配提出一个模式吗?

using ISO9660.Logical;

namespace ISO9660.Physical;

public static class DiscExtensions
{
    public static async Task ReadFileRawAsync(this Disc disc, IsoFileSystemEntryFile file, Stream stream)
    {
        await ReadFileAsync(disc, file, stream, ReadFileRaw);
    }

    public static async Task ReadFileUserAsync(this Disc disc, IsoFileSystemEntryFile file, Stream stream)
    {
        await ReadFileAsync(disc, file, stream, ReadFileUser);
    }

    private static async Task ReadFileAsync(Disc disc, IsoFileSystemEntryFile file, Stream stream, ReadFileHandler handler)
    {
        int position = (int)file.Position;

        Track track = disc.Tracks.FirstOrDefault(s => position >= s.Position)
                      ?? throw new InvalidOperationException("Failed to determine track for file.");

        int sectors = (int)Math.Ceiling((double)file.Length / track.Sector.GetUserDataLength());

        for (int i = position; i < position + sectors; i++)
        {
            ISector sector = await track.ReadSectorAsync(i);

            byte[] array = handler(file, stream, sector).ToArray(); // sucks

            await stream.WriteAsync(array);
        }
    }

    private static Span<byte> ReadFileRaw(IsoFileSystemEntryFile file, Stream stream, ISector sector)
    {
        Span<byte> data = sector.GetData();

        int size = data.Length;

        Span<byte> span = data[..size];

        return span;
    }

    private static Span<byte> ReadFileUser(IsoFileSystemEntryFile file, Stream stream, ISector sector)
    {
        Span<byte> data = sector.GetUserData();

        int size = (int)Math.Min(Math.Max(file.Length - stream.Length, 0), data.Length);

        Span<byte> span = data[..size];

        return span;
    }

    private delegate Span<byte> ReadFileHandler(IsoFileSystemEntryFile file, Stream stream, ISector sector);
}

ISector 接口:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

namespace ISO9660.Physical;

public interface ISector
{
    int Length { get; }

    Span<byte> GetData();

    Span<byte> GetUserData();

    int GetUserDataLength();

    public static Span<byte> GetSpan<T>(scoped ref T sector, int start, int length)
        where T : struct, ISector
    {
        var span = MemoryMarshal.CreateSpan(ref sector, 1);

        var bytes = MemoryMarshal.AsBytes(span);

        var slice = bytes.Slice(start, length);

        return slice;
    }
    
    public static ISector Read<T>(Stream stream)
        where T : struct, ISector
    {
        var size = Unsafe.SizeOf<T>();

        Span<byte> span = stackalloc byte[size];

        stream.ReadExactly(span);

        var read = MemoryMarshal.Read<T>(span);

        return read;
    }

    public static async Task<ISector> ReadAsync<T>(Stream stream)
        where T : struct, ISector
    {
        var buffer = new byte[Unsafe.SizeOf<T>()];

        await stream.ReadExactlyAsync(buffer);

        var sector = MemoryMarshal.Read<T>(buffer);

        return sector;
    }
}

ISector 实现示例:

using JetBrains.Annotations;

namespace ISO9660.Physical;

public unsafe struct SectorRawMode1 : ISector
{
    private const int UserDataLength = 2048;

    private const int UserDataPosition = 16;

    [UsedImplicitly]
    public fixed byte Sync[12];

    [UsedImplicitly]
    public fixed byte Header[4];

    [UsedImplicitly]
    public fixed byte UserData[UserDataLength];

    [UsedImplicitly]
    public fixed byte Edc[4];

    [UsedImplicitly]
    public fixed byte Intermediate[8];

    [UsedImplicitly]
    public fixed byte PParity[172];

    [UsedImplicitly]
    public fixed byte QParity[104];

    public readonly int Length => 2352;

    public Span<byte> GetData()
    {
        return ISector.GetSpan(ref this, 0, Length);
    }

    public Span<byte> GetUserData()
    {
        return ISector.GetSpan(ref this, UserDataPosition, UserDataLength);
    }

    public readonly int GetUserDataLength()
    {
        return UserDataLength;
    }
}
C# 异步 内存管理

评论

3赞 Dai 11/7/2023
请将所有关键字替换为其实际的编译时静态类型。var
0赞 Dai 11/7/2023
你没有通过有什么原因吗?CancellationToken
1赞 Dai 11/7/2023
var position = (int)file.Position;<-- ISO 9660 允许最大长度为 8TB 的卷,但您的代码假定有 32 位 (4GB) 的限制。你应该在这里使用。Int64
0赞 Dai 11/7/2023
什么?ReadFileHandler
1赞 Alander 11/7/2023
stackoverflow.com/questions/57229123/ 的潜在重复...

答:

2赞 Joshua 11/7/2023 #1

更改为并向上推送更改。 类似于异步(在实践中)。这个想法不像 a , a 可以存在于堆上(因此不能指代 )。Span<T>Memory<T>Memory<T>Span<T>SpanMemorystackalloc

没有,所以你必须用老式的方式去做:固定,获取地址,并构造一个指针。如何执行此操作的示例: https://stackoverflow.com/a/52191681/14768 请注意,MemoryManager 分配远小于缓冲区分配,因此无需复制。MemoryMarshal.CreateMemoryMemory

在编写代码时要非常小心;这个 MemoryManager 的构造函数确实接受一个 是整个 ,并且它将在调用中存在;但是,如果您未能处理它,则会泄漏内存。此外,如果它们来自堆栈而不是堆,那么它就在你身上,你会丢弃堆栈。SpanISectorasyncISector