c# - 使用 Web API 下载大文件返回 OutOfMemoryException

标签 c# asp.net asp.net-web-api

我尝试过在 SO 上使用大量问题/答案 - 但似乎无法克服在尝试通过 Web API 下载 200MB zip 文件时收到的 OutOfMemoryException 问题。

为了测试我已经大大简化了我的代码:

    [HttpPost]
    public async Task<HttpResponseMessage> ExportReports(OrderExportFilter filterJson)
    {

        var filename = "C:\\pdftemp\\1128d0ff-a4b7-440d-9e3b-dd152445eb62.zip";

        var fileStream = File.OpenRead(filename);

        var content = new StreamContent(fileStream, 4096);

        resp.Content = content;

        resp.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
        resp.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
        {
            FileName = "OrdersExport.pdf"
        };

        return resp;
     }

这只是我尝试下载 zip 文件的多种方法之一,我还 referenced this link并重复了代码但无济于事。

我不知道我做错了什么才能下载大型 zip 文件。

更新:根据评论中的请求,我尝试使用“application/octet-stream”代替 - 仍然没有运气 - 同样的错误。

另外 - 还有一件事需要注意 - 当我使用此代码下载较小的 zip 文件时,它工作正常,似乎一旦文件太大就开始轰炸。

异常更新:

我能够提取异常详细信息:

    {
        "Message": "An error has occurred.",
        "ExceptionMessage": "Exception of type 'System.OutOfMemoryException' was thrown.",
        "ExceptionType": "System.OutOfMemoryException",
        "StackTrace": "   at System.IO.MemoryStream.set_Capacity(Int32 value)\r\n   at System.IO.MemoryStream.EnsureCapacity(Int32 value)\r\n   at System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count)\r\n   at System.IO.Compression.DeflateStream.WriteDeflaterOutput(Boolean isAsync)\r\n   at System.IO.Compression.DeflateStream.PurgeBuffers(Boolean disposing)\r\n   at System.IO.Compression.DeflateStream.Dispose(Boolean disposing)\r\n   at System.IO.Stream.Close()\r\n   at System.IO.Compression.GZipStream.Dispose(Boolean disposing)\r\n   at System.IO.Stream.Close()\r\n   at System.IO.Stream.Dispose()\r\n   at System.Net.Http.Extensions.Compression.Core.Compressors.BaseCompressor.<Compress>d__4.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Net.Http.Extensions.Compression.Core.Models.CompressedContent.<SerializeToStreamAsync>d__4.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Owin.HttpMessageHandlerAdapter.<BufferResponseContentAsync>d__13.MoveNext()"
    }

最佳答案

您的堆栈跟踪表明关闭 DeflateStream 时引发异常。该流显然会写入 MemoryStream 并关闭 DeflateStream 将整个(压缩的)响应输出到 MemoryStream。也就是说,MemoryStream 中的字节数组缓冲区已分配,如果压缩输出太大,则会失败并出现 OutOfMemoryException

压缩后的响应缓冲在内存中有点令人惊讶,因为网络服务器至少在概念上能够通过压缩器将文件中的数据流式传输到网络,而无需先将整个文件读取并压缩到内存中。

从评论中我猜测您正在 Web API 应用程序中使用 NuGet 包 Microsoft.AspNet.WebApi.Extensions.Compression.Server 。虽然我不能声称我完全理解它是如何工作的,但我确实尝试对其进行反编译,从我所看到的情况来看,通过这个中间件发送响应会导致输出被缓冲不是一次而是实际上两次。 (BaseCompressor.Compress() 方法写入由 StreamManager.GetStream() 创建的 MemoryStream,并且此 MemoryStream然后读入字节数组,有效地使压缩器的内存需求加倍。)

根据此分析,您应该关闭大响应的压缩,这似乎通过将属性 [Compression(Enabled = false)] 应用于您的 Controller 操作来解决您的问题。

有点讽刺的是,您无法压缩大响应,而这些响应可能会从压缩中受益最多。但是,如果您在 native 支持响应压缩的 Web 服务器(例如 IIS)上运行 Web API,则可以完全删除中间件并在 Web 服务器中打开压缩(在 IIS 中称为 dynamic compression)。 native 压缩的性能可能也比托管代码中的压缩更好。

关于c# - 使用 Web API 下载大文件返回 OutOfMemoryException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47661996/

相关文章:

c# - 删除内联静态事件

c# - NullReferenceException 条件空检查?

asp.net - 您如何从请求的 URI 中获取某些属性?

jquery - blueimp jQuery 文件上传多个实例

asp.net - Ajax ConfirmButtonExtender 在 OnClientClick javascript 代码之前触发

asp.net - OAuth2 Web Api token 到期

c# - 使用 MSBuild 14 时禁用代码分析

c# - 程序集使用版本 X,其版本高于引用的程序集错误

http - 为什么我看不到 HttpUtility.ParseQueryString 方法?

asp.net - 数据注释用于在记录之前清理请求和响应