异步方法中如何避免调用Span<T>.ToArray()以减少内存分配?
异步改造后Span的无内存分配解决方案
问题出在哪
- Span
没法在async里用 :Span依赖栈或固定内存,异步方法切换状态时栈上下文会丢失,保留Span引用存在安全风险。 - ToArray()导致频繁分配:原代码每次循环都把Span转成字节数组,持续产生堆内存分配,GC压力大。
- 装箱性能坑:ISector接口让值类型
SectorRawMode1被装箱,不仅拖慢性能,还让GetSpan拿到的Span指向临时栈副本,异步场景下容易出问题。
怎么改
1. 换个思路:直接写流,不返回Span
把原来返回Span的委托改成直接负责往目标流写数据,跳过转数组的步骤,直接用Stream.WriteAsync(ReadOnlyMemory<byte>)完成无分配写入。
2. 避免装箱:用泛型返回具体值类型
把ReadSectorAsync改成泛型方法,返回具体的扇区struct类型,彻底消除接口带来的装箱开销,同时保证内存引用安全。
3. 异步读用内存池:消除临时数组分配
把ISector.ReadAsync里的临时byte[]换成ArrayPool<byte>复用,彻底干掉异步读取时的内存分配。
修改后的完整代码
1. DiscExtensions类重构
using ISO9660.Logical; using System.Buffers; namespace ISO9660.Physical; public static class DiscExtensions { public static async Task ReadFileRawAsync<T>(this Disc disc, IsoFileSystemEntryFile file, Stream stream) where T : struct, ISector { await ReadFileAsync(disc, file, stream, ReadFileRawAsync<T>); } public static async Task ReadFileUserAsync<T>(this Disc disc, IsoFileSystemEntryFile file, Stream stream) where T : struct, ISector { await ReadFileAsync(disc, file, stream, ReadFileUserAsync<T>); } private static async Task ReadFileAsync<T>(Disc disc, IsoFileSystemEntryFile file, Stream stream, Func<IsoFileSystemEntryFile, Stream, T, ValueTask> handler) where T : struct, ISector { 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++) { // 读取具体值类型扇区,避免装箱 T sector = await track.ReadSectorAsync<T>(i); // 直接写入流,无中间内存分配 await handler(file, stream, sector); } } private static ValueTask ReadFileRawAsync<T>(IsoFileSystemEntryFile file, Stream stream, T sector) where T : struct, ISector { ReadOnlyMemory<byte> data = sector.GetData(); return stream.WriteAsync(data); } private static ValueTask ReadFileUserAsync<T>(IsoFileSystemEntryFile file, Stream stream, T sector) where T : struct, ISector { ReadOnlyMemory<byte> data = sector.GetUserData(); int remaining = (int)(file.Length - stream.Length); int writeSize = Math.Min(remaining, data.Length); return stream.WriteAsync(data[..writeSize]); } }
2. ISector接口调整
using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace ISO9660.Physical; public interface ISector { int Length { get; } ReadOnlyMemory<byte> GetData(); ReadOnlyMemory<byte> GetUserData(); int GetUserDataLength(); public static ReadOnlyMemory<byte> GetMemory<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); // 将堆内存Span转为Memory,无额外分配 return MemoryMarshal.AsMemory(bytes.Slice(start, length)); } public static T Read<T>(Stream stream) where T : struct, ISector { var size = Unsafe.SizeOf<T>(); Span<byte> span = stackalloc byte[size]; stream.ReadExactly(span); return MemoryMarshal.Read<T>(span); } public static async Task<T> ReadAsync<T>(Stream stream) where T : struct, ISector { var size = Unsafe.SizeOf<T>(); // 从内存池借缓冲区,用完归还 byte[] buffer = ArrayPool<byte>.Shared.Rent(size); try { await stream.ReadExactlyAsync(buffer.AsMemory(0, size)); return MemoryMarshal.Read<T>(buffer.AsSpan(0, size)); } finally { ArrayPool<byte>.Shared.Return(buffer); } } }
3. SectorRawMode1实现更新
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 ReadOnlyMemory<byte> GetData() { return ISector.GetMemory(ref this, 0, Length); } public ReadOnlyMemory<byte> GetUserData() { return ISector.GetMemory(ref this, UserDataPosition, UserDataLength); } public readonly int GetUserDataLength() { return UserDataLength; } }
4. Track类的ReadSectorAsync改泛型
public async Task<T> ReadSectorAsync<T>(int index) where T : struct, ISector { // 这里添加你的扇区定位逻辑,比如移动流到指定位置 // _stream.Seek(...); return await ISector.ReadAsync<T>(_stream); }
为啥这么改
- 彻底消除内存分配:直接用
ReadOnlyMemory写流,跳过ToArray()的堆分配;异步读用内存池复用缓冲区,无临时数组分配。 - 干掉装箱开销:泛型返回具体值类型,避免ISector接口带来的装箱操作,性能提升明显。
- 异步场景安全:用
ReadOnlyMemory替代Span<T>,适配异步状态机的堆内存存储,不会出现内存引用失效的问题。
内容的提问来源于stack exchange,提问作者aybe
相关产品推荐
相关产品推荐

