我有一个.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存储的交互抽象到它自己的服务中,但也许我已经让自己变得更加困难了。
答案 0 :(得分:1)
发现这两个代码示例非常好地说明了支持您的用例的概念。
ZIP压缩级别:
zipOutputStream.SetLevel(3); //0-9, 9 being the highest level of compression
End-to-end example using ASP.NET WebApi
进一步阅读
答案 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");
}
}
我希望这对那些只需要朝着正确方向发展的人有用。我最初采取了一种懒惰的方式,它又回来咬我。