下载视频文件时内存不足异常

Out Of Memory Exception when downloading a video file

我们目前从 Twilio 下载视频,下载视频后我们将视频保存在 AWS S3 中。但是,在部署到生产环境时,我目前得到:

Exception of type 'System.OutOfMemoryException' was thrown.

尝试下载视频时,

我检查了 Twilio 中的视频大小,它们相对较小,172,912kb

我已经将 S3 上的实例升级到大型实例,因为我认为这会是一个问题,因为我们以前是小型实例。

但是问题依然存在,失败的代码块如下:

 var request = (HttpWebRequest)WebRequest.Create($"{resource.Url}/Media");
 request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(_twilioOptions.ApiKey + ":" + _twilioOptions.ApiSecret)));
 request.AllowAutoRedirect = true;

 var responseBody = (request.GetResponseAsync().Result).GetResponseStream();

 byte[] tempBuffer = new byte[8192];
 MemoryStream ms = new MemoryStream();
 int read;
 while ((read = responseBody.Read(tempBuffer, 0, tempBuffer.Length)) > 0)
 {
    ms.Write(tempBuffer, 0, read);
 }

 var result = ms;
    
 var fileName = $"Conversations/{conversationId}/Video-{DateTime.UtcNow:yyyyMMdd-hhmmss}-{compositionSid}.{resource.Format}";
 var uploadedFile = await _fileUploader.UploadFile(result, fileName, _s3Options.SecureBucket, "video/mp4");

任何人都可以为此推荐解决方案/修复方法吗?

正如您可能已经猜到的那样,如果视频很大,将整个视频保存在内存中是个坏主意。

尽管 180mb 看起来并不多,但它仍然需要一个连贯的内存区域用于 LOH 中的内存区域。使用一些按块写入磁盘的方法可能会更好。为了提高性能和避免 LOH 中的内存碎片,您也可以顺便使用 RecyclableMemoryStream,但这对您当前的系统没有任何作用。

通常你应该避免这种设计。保存到磁盘(使用内存缓冲)并信任小文件的文件系统缓存,或者尝试直接将下游馈送到上游。

理论上,在 S37GB RAM 计划)中一次将 ~170MB 的文件全部下载到内存中应该没有问题。但是,您不是处​​置或关闭您的任何资源。根据周围的代码,这很可能是未释放非托管资源的问题,并可能导致一段时间内内存不足。

现在我们可以对 Close()Dispose() 进行显式调用,但更容易将相关代码封装在 using() 块中,如下所示:

 
 byte[] tempBuffer = new byte[8192];
 using (var responseBody = (request.GetResponseAsync().Result).GetResponseStream()) 
 using (var ms = new MemoryStream()) // <------- using block, will call Dispose() 
 {
     int read;
     while ((read = responseBody.Read(tempBuffer, 0, tempBuffer.Length)) > 0)
     {
        ms.Write(tempBuffer, 0, read);
     }

     var result = ms;
    
     var fileName = $"Conversations/{conversationId}/Video-{DateTime.UtcNow:yyyyMMdd-hhmmss}-{compositionSid}.{resource.Format}";
     var uploadedFile = await _fileUploader.UploadFile(result, fileName, 
                                                      _s3Options.SecureBucket, "video/mp4");
}


您的代码类似于 this MSDN 示例,但是您会注意到 Close().

的使用

Tell me more

感谢 mjwills 发现 responseBody 也可以放入 using() 块中,从而节省显式 Close.

前进

即使有了这些修复,您可能仍希望利用流式资源的最佳做法,而不是一次加载所有资源,尤其是在不需要一次将所有资源加载到内存中的情况下。只需为源创建一个读取流,为目标创建一个写入流。那就简单的一次读写块了。