ASP.NET MVC / WEB API文件上载:上载后内存未被释放

时间:2015-08-14 19:19:10

标签: c# asp.net-mvc file-upload asp.net-web-api memory-leaks

我正在研究用户上传文件的项目可能存在的内存泄漏问题。这些文件通常是.zip或.exe压缩文件,用于其他软件。文件的平均大小为80MB

有一个MVC应用程序,它具有上传文件的界面(View)。此视图向控制器内的操作发送POST请求。此控制器操作使用与此类似的MultipartFormDataContent获取文件:Sending binary data along with a REST API request并且:WEB API FILE UPLOAD, SINGLE OR MULTIPLE FILES

在操作中,我获取文件并将其转换为字节数组。转换后,我使用byte []数组向我的API发送一个post请求。

这是执行该操作的MVC APP代码:

[HttpPost]
    public async Task<ActionResult> Create(ReaderCreateViewModel model)
    {
        HttpPostedFileBase file = Request.Files["Upload"];

        string fileName = file.FileName;

        using (var client = new HttpClient())
        {
            using (var content = new MultipartFormDataContent())
            {                   
                using (var binaryReader = new BinaryReader(file.InputStream))
                {
                    model.File = binaryReader.ReadBytes(file.ContentLength);
                }

                var fileContent = new ByteArrayContent(model.File);
                fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                {
                    FileName = file.FileName
                };
                content.Add(fileContent);

                var requestUri = "http://localhost:52970/api/upload";
                HttpResponseMessage response = client.PostAsync(requestUri, content).Result;

                if (response.IsSuccessStatusCode)
                {                       
                    return RedirectToAction("Index");
                }
            }
        }

        return View("Index", model);
    }

在调查使用如下的几个记忆工具后:Best Practices No. 5: Detecting .NET application memory leaks 我发现在将该文件转换为此行的字节数组之后:

using (var binaryReader = new BinaryReader(file.InputStream))
{
      model.File = binaryReader.ReadBytes(file.ContentLength);
}

内存使用量从70MB +或 - 增加到175mb +或 - 甚至在发送和完成请求后,内存永远不会被释放。如果我继续上传文件,内存会不断增加,直到服务器完全关闭。

我们无法将文件直接从多部分表单发送到API,因为我们需要在(业务要求/规则)之前发送和验证某些数据。经过研究,我采用了这种方法,但内存泄漏问题与我有关。

我错过了什么吗?垃圾收集器应该立即收集内存吗?在所有一次性对象中,我使用的是“使用”语法,但它没有帮助。

我也对这种上传文件的方法感到好奇。我应该以不同的方式做吗?

为了澄清,API与MVC应用程序是分开的(每个应用程序都托管在IIS中的一个单独的网站上),它全部都在C#中。

1 个答案:

答案 0 :(得分:2)

<强> 1。垃圾收集器应该立即收集内存吗?

垃圾收集器不会立即释放内存,因为这是一项耗时的操作。发生垃圾收集时,将暂停所有应用程序的托管线程。这会引入不必要的延迟因此,垃圾收集器仅基于复杂的算法偶尔执行。

<强> 2。在所有一次性对象中,我使用的是“使用”语法,但它没有帮助。

using语句处理供应有限的非托管资源 (通常与IO相关,如文件句柄,数据库和网络连接)。因此,此语句不会影响垃圾回收。

第3。我错过了什么吗?

在用ByteArrayContent包装后,看起来你不需要原始的字节数组。在包装后不会清除model.File,并且数组最终可以传递到Index视图。

我会替换:

using(var binaryReader = new BinaryReader(file.InputStream)) {
    model.File = binaryReader.ReadBytes(file.ContentLength);
}
var fileContent = new ByteArrayContent(model.File);

使用:

ByteArrayContent fileContent = null;
using(var binaryReader = new BinaryReader(file.InputStream)) {
    fileContent = new ByteArrayContent(binaryReader.ReadBytes(file.ContentLength));
}

以避免明确地清理model.File

<强> 4。如果我继续上传文件,内存会不断增加,直到服务器完全关闭。

如果您的文件平均为80MB,则它们最终会出现在大对象堆上。堆不会自动压缩,通常不会被垃圾回收。看起来在你的情况下,大对象堆无限增长(可能会发生)。

如果您正在使用(或可以升级到).NET 4.5.1或更高版本,则可以通过设置强制大型对象堆来压缩:

System.Runtime.GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;

每次要在下一个完整垃圾回收集中安排大对象堆压缩时,都需要调用这行代码。

您还可以通过调用:

强制立即压缩
System.Runtime.GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
System.GC.Collect();

但是,如果你需要释放大量内存,那么从时间上来说这将是一项代价高昂的操作。