如何避免LOH分配并解决ArrayPool<T>租聘数组过大的上传问题?
解决ArrayPool分块上传时数组尺寸过大的问题
核心问题是ArrayPool<T>.Shared.Rent()返回的数组可能大于请求的chunkSize,但我们只需要用到前chunkSize(或实际读取的字节数)的部分,不需要传递整个数组。以下是两种可行的解决方案,都不会分配新数组:
方案1:基于实际读取字节数传输切片
利用ReadAsync的返回值获取实际读取的字节数,然后仅传输对应长度的内存片段,完全避免传递多余的字节:
while (someflag) { reader.Seek(counterHowMuchIsTransferred, SeekOrigin.Begin); var buffer = ArrayPool<byte>.Shared.Rent(chunkSize); try { // 限制读取范围为请求的chunkSize,避免读取到数组多余部分 int bytesRead = await reader.ReadAsync(buffer.AsMemory(0, chunkSize), cancellationToken); if (bytesRead == 0) break; // 文件已读取完毕 // 上传时仅传递实际读取的字节片段,而非整个数组 await UploadChunkAsync(buffer.AsMemory(0, bytesRead), cancellationToken); counterHowMuchIsTransferred += bytesRead; // 如果是最后一块(读取字节数小于chunkSize),终止循环 if (bytesRead < chunkSize) someflag = false; } finally { // 务必归还数组到池,防止内存泄漏 ArrayPool<byte>.Shared.Return(buffer); } }
方案2:严格保证块大小(除最后一块)
如果API要求除最后一块外,其余块必须严格等于chunkSize,可以循环读取直到填满chunkSize或文件结束:
while (someflag) { reader.Seek(counterHowMuchIsTransferred, SeekOrigin.Begin); var buffer = ArrayPool<byte>.Shared.Rent(chunkSize); try { int totalBytesRead = 0; // 循环读取直到凑够chunkSize字节或文件结束 while (totalBytesRead < chunkSize) { int bytesRead = await reader.ReadAsync( buffer.AsMemory(totalBytesRead, chunkSize - totalBytesRead), cancellationToken ); if (bytesRead == 0) break; totalBytesRead += bytesRead; } if (totalBytesRead == 0) break; // 传输对应长度的内存片段 await UploadChunkAsync(buffer.AsMemory(0, totalBytesRead), cancellationToken); counterHowMuchIsTransferred += totalBytesRead; // 最后一块不足chunkSize,终止循环 if (totalBytesRead < chunkSize) someflag = false; } finally { ArrayPool<byte>.Shared.Return(buffer); } }
关键注意事项
- 无论哪种方案,都要在
finally块中归还数组到ArrayPool,否则会造成内存泄漏 - 使用
AsMemory()创建内存切片不会分配新数组,只是生成一个指向原数组指定范围的视图,完全符合避免LOH分配的需求 - 永远不要直接传递
ArrayPool借出的整个数组,必须根据实际读取长度切片后再传输
内容的提问来源于stack exchange,提问作者Expressingx
相关产品推荐
相关产品推荐

