C# 正在使用字节数组创建对象列表:OutOfMemoryException

C# 正在使用字节数组创建对象列表:OutOfMemoryException,c#,azure,.net-core,azure-storage-blobs,C#,Azure,.net Core,Azure Storage Blobs,我有一个.NETCore1.1应用程序,它在生成包含字节数组的对象列表时遇到问题。如果列表中有超过20项(任意,我不确定失败的确切数目或大小),该方法将抛出OutOfMemoryException。方法如下: public async Task<List<Blob>> GetBlobsAsync(string container) { List<Blob> retVal = new List<Blob>();

我有一个.NETCore1.1应用程序,它在生成包含字节数组的对象列表时遇到问题。如果列表中有超过20项(任意,我不确定失败的确切数目或大小),该方法将抛出OutOfMemoryException。方法如下:

public async Task<List<Blob>> GetBlobsAsync(string container)
    {
        List<Blob> retVal = new List<Blob>();
        Blob itrBlob;
        BlobContinuationToken continuationToken = null;
        BlobResultSegment resultSegment = null;

        CloudBlobContainer cont = _cbc.GetContainerReference(container);
        resultSegment = await cont.ListBlobsSegmentedAsync(String.Empty, true, BlobListingDetails.Metadata, null, continuationToken, null, null);
        do
        {
            foreach (var bItem in resultSegment.Results)
            {
                var iBlob = bItem as CloudBlockBlob;
                itrBlob = new Blob()
                {
                    Contents = new byte[iBlob.Properties.Length],
                    Name = iBlob.Name,
                    ContentType = iBlob.Properties.ContentType
                };

                await iBlob.DownloadToByteArrayAsync(itrBlob.Contents, 0);

                retVal.Add(itrBlob);
            }

            continuationToken = resultSegment.ContinuationToken;

        } while (continuationToken != null);

        return retVal;
    }
public异步任务GetBlobsAsync(字符串容器)
{
List retVal=新列表();
Blob-itrBlob;
BlobContinuationToken continuationToken=null;
BlobResultSegment resultSegment=null;
CloudBlobContainer cont=_cbc.GetContainerReference(容器);
resultSegment=await cont.ListBlobsSegmentedAsync(String.Empty,true,BlobListingDetails.Metadata,null,continuationToken,null,null);
做
{
foreach(resultSegment.Results中的变量bItem)
{
var iBlob=bItem作为CloudBlockBlob;
itrBlob=新Blob()
{
Contents=新字节[iBlob.Properties.Length],
Name=iBlob.Name,
ContentType=iBlob.Properties.ContentType
};
等待iBlob.downloadtobytearayasync(itrBlob.Contents,0);
retVal.Add(itrBlob);
}
continuationToken=resultSegment.continuationToken;
}while(continuationToken!=null);
返回返回;
}
我没有使用任何可以在方法中处理的东西。有没有更好的方法来实现这一点?最终目标是提取所有这些文件,然后创建一个ZIP存档。只要我不超过某个大小阈值,这个过程就可以工作

如果有帮助,应用程序将从Azure Web应用程序实例访问Azure Block Blob存储。也许有一个设置我需要调整以增加阈值

实例化Blob()对象时引发异常

编辑:
因此,发布的问题在细节方面无疑是薄弱的。问题容器有30个文件(大部分是压缩良好的大型文本文件)。容器的总大小为971MB。在报告HTTP 500错误和引用的异常之前,请求运行约40秒

当我在本地调试并单步执行相同的操作时,它会成功,生成237MB的zip文件。在操作过程中,我可以看到创建列表时内存使用量超过2GB


我试图将blob存储的交互抽象到它自己的服务中,但可能是我自己让这变得更加困难。

发现这两个代码示例非常好地说明了支持您的用例的概念

压缩级别:

zipOutputStream.SetLevel(3); //0-9, 9 being the highest level of compression

  • 可以在这个结构良好的应用程序中添加Zip功能
进一步阅读


根据萨沙的回答,我做出了一个折衷方案,在给定参数的情况下,该方案似乎表现得不错。可能并不完美,但它将内存使用量减少了近70%,并允许我保留一些抽象

我向blob服务添加了一个名为GetBlobsAsZipAsync的方法,该方法接受容器名称作为参数:

public async Task<Stream> GetBlobsAsZipAsync(string container)
    {
        BlobContinuationToken continuationToken = null;
        BlobResultSegment resultSegment = null;
        byte[] buffer = new byte[4194304];
        MemoryStream ms = new MemoryStream();

        CloudBlobContainer cont = _cbc.GetContainerReference(container);
        resultSegment = await cont.ListBlobsSegmentedAsync(String.Empty, true, BlobListingDetails.Metadata, null, continuationToken, null, null);

        using (var za = new ZipArchive(ms, ZipArchiveMode.Create, true))
        {
            do
            {
                foreach (var bItem in resultSegment.Results)
                {
                    var iBlob = bItem as CloudBlockBlob;

                    var ze = za.CreateEntry(iBlob.Name);
                    using (var fs = await iBlob.OpenReadAsync())
                    {
                        using (var dest = ze.Open())
                        {
                            int count = await fs.ReadAsync(buffer, 0, buffer.Length);
                            while (count > 0)
                            {
                                await dest.WriteAsync(buffer, 0, count);
                                count = await fs.ReadAsync(buffer, 0, buffer.Length);
                            }
                        }                                
                    }
                }

                continuationToken = resultSegment.ContinuationToken;

            } while (continuationToken != null);
        }

        return ms;
    }

我希望这对其他需要朝正确方向努力的人有用。最初我懒散地处理了这个问题,结果它又咬了我一口。

你是在32位模式下运行的吗?然后突破32位的限制?无论如何,如果这些文件如此之大,为什么不将它们保存到一些持久性存储中,并传递链接,而不是将整个文件保存在内存中?问题容器中有30个文件,总容量为971MB。在大多数情况下,这些文件由链接引用,但此函数用于将所有相关文件作为ZIP文件下载。大多数文件集都小于50MB,这一个文件集只比其他文件集大很多。应用程序以64位模式运行。尽管如此,我仍然在2GB以下。正如Michael所说,问题很可能是你在Azure上以32位模式运行;这是默认的。正如您所说,当您在本地运行它时,您可以看到内存超过2gb,这并不奇怪,因为您正在将blob下载到内存并将其复制到字节数组。。。64位只是一种变通方法。简而言之,您应该将每个Bob作为流下载,并立即将其写入zip编写器,然后将其处理掉。您还可以让zip编写器写入临时文件,而不是内存流。说到IO和内存压力,抽象不是你的朋友:)你还需要帮助吗?根据您的回答,我可以提供代码示例,以使这项工作具有较低的内存占用。此外,在web应用程序中以这种方式创建Zip文件通常会导致HTTP请求超时,而且扩展性非常差。希望您能从给定的示例中获益,我自己还没有时间编译工作示例代码,如果您在应用此应用程序时遇到问题,可能稍后再进行。
[HttpPost]
    public async Task<IActionResult> DownloadFiles(string container, int projectId, int? profileId)
    {
        MemoryStream ms = null;

        _ctx.Add(new ProjectDownload() { ProfileId = profileId, ProjectId = projectId });
        await _ctx.SaveChangesAsync();

        using (ms = (MemoryStream)await _blobs.GetBlobsAsZipAsync(container))
        {
            return File(ms.ToArray(), "application/zip", "download.zip");             
        }
    }