2015-08-14 43 views
4

我正在调查用户上传文件的项目中可能发生的内存泄漏问题。这些文件通常是用于其他软件的.zip或.exe压缩文件。这些文件的平均大小为80MBASP.NET MVC/WEB API文件上传:上传后没有释放内存

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

在动作中,我得到该文件并将其转换为字节数组。转换后,我用byte []数组发送一个post请求到我的API。

这里是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); 
    } 

使用多种存储工具,如该调查后: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,因为我们需要在(业务需求/规则)之前发送和验证一些数据。经过研究,我已经使用了这种方法,但内存泄漏问题与我有关。

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

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

只是为了澄清,API与MVC应用程序(每个托管在IIS中的独立Web站点上)分开,并且全部都在C#中。

回答

2

1.垃圾收集器应该立即收集内存吗?

垃圾回收器不会立即释放内存,因为这是一项耗时的操作。当垃圾收集发生时,所有应用程序的管理线程都会暂停。这引入了不需要的延迟。所以,垃圾收集器只能根据复杂的算法偶尔发挥作用。

2.在所有可丢弃的对象中,我使用“using”语法,但它没有帮助。

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

3.我错过了什么吗?

它看起来像你不需要原始字节数组后,你用ByteArrayContent包装它。在包装完成后,您不会清除model.File,并且数组最终可能会传递到索引视图。

我将取代:

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

然而,如果你需要释放大量的内存,这将是在时间上昂贵的操作。