使用字节数组创建对象列表:OutOfMemoryException

时间:2017-10-13 20:58:45

标签: c# azure .net-core azure-storage-blobs

我有一个.NET Core 1.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;
    }

我没有使用任何可以在方法中真正处理的东西。有没有更好的方法来实现这一目标?最终目标是提取所有这些文件,然后创建ZIP存档。只要我没有达到某个规模的门槛,这个过程就会起作用。

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

实例化Blob()对象时抛出异常。

编辑: 所以发布的问题在细节方面确实很弱。问题容器有30个文件(大多数文本文件压缩得很好)。容器的总大小为971MB。在报告HTTP 500错误和引用的异常之前,请求运行大约40秒。

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

我试图将blob存储的交互抽象到它自己的服务中,但也许我已经让自己变得更加困难了。

2 个答案:

答案 0 :(得分:1)

答案 1 :(得分:0)

使用Sascha的答案,我能够做出一个似乎在给定参数下表现得很好的折衷方案。可能并不完美,但它将内存使用量减少了近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;
    }

这会将Zip作为(已关闭的)MemoryStream返回,然后使用FileResult将其作为数组返回:

[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");             
        }
    }

我希望这对那些只需要朝着正确方向发展的人有用。我最初采取了一种懒惰的方式,它又回来咬我。