ASP.NET MVC/WEB API文件上传:上传后内存没有被释放

4
我正在调查一个用户上传文件时可能存在的内存泄漏问题。这些文件通常是用于其他软件的.zip或.exe压缩文件,平均大小为80MB。
有一个MVC应用程序具有上传文件(视图)的界面。此视图向控制器中的操作发送POST请求。此控制器操作使用类似于此在REST API请求中发送二进制数据和此WEB API文件上传,单个或多个文件的MultipartFormDataContent获取文件。
在操作中,我获取文件并将其转换为字节数组。转换后,我使用字节数组向我的API发送POST请求。
这里是MVC应用程序执行此操作的代码:
[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);
    }

在研究了多种内存工具后,例如:最佳实践5:检测.NET应用程序的内存泄漏,我发现问题出现在将文件转换为字节数组的这一行代码:

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

内存使用量从大约70MB +增加到175MB +,即使在发送和完成请求后,内存也永远不会被释放。如果我继续上传文件,内存就会持续增加,直到服务器完全崩溃。
我们无法直接从多部分表单向API发送文件,因为我们需要在发送和验证一些数据之前(业务要求/规则)进行。经过研究,我已经提出了这种方法,但内存泄漏问题令我担忧。
我是否遗漏了什么?垃圾收集器应该立即回收内存吗?在所有可处理的对象中,我都使用“using”语法,但这并没有帮助。
我还对这种上传文件的方法很好奇。我应该采用不同的方式吗?
仅供澄清,API与MVC应用程序分离(每个应用程序在IIS中托管在一个单独的网站上),并且全部使用C#。
1个回答

2

1. 垃圾回收器是否应该立即回收内存?

垃圾回收器不会立即释放内存,因为这是一个耗时的操作。当进行垃圾回收时,所有应用程序的托管线程都会暂停。这会导致不必要的延迟。因此,垃圾回收器仅根据复杂的算法偶尔执行。

2. 我在使用所有可处理的对象时都使用了“using”语法,但没有起到作用。

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();

然而,如果您需要释放大量内存,这将是一个时间成本高昂的操作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接