2011-07-29 59 views
1

我的一个IIS应用程序池进程有一个非常奇怪的问题。我最近得到了一个System.OutOfMemoryException错误,并一直试图弄清楚到底发生了什么。基本上我有一个脚本,它使用Web服务从我们的DAM获取文件。然后它检查文件将其存储为一个字节数组,然后使用Response输出该文件。唯一存在问题的是当PDF超过20MB时,它似乎有时会导致错误。如果我增加应用程序池中的内存,它会暂时解决问题。我观察了w3wp.exe进程,发现有时当我运行这个脚本时,它将内存增加到400MB,我们拥有的最大文件是45MB,会导致这种行为发生。这个问题似乎每天晚上都会消失,早上它会工作一段时间,然后再次开始做同样的事情。这个应用程序是c#asp.net应用程序。它运行在共享点内部。使用大量内存的IIS应用程序池进程

看了一段时间的服务后,我注意到,因为这些PDF在浏览器窗口中呈现,直到文件完全下载,它不会从内存中释放。这是有道理的,但我可以看到这是我的问题。如果我有几个人加载文件,平均(无文件下载)内存使用量为385,000 kb,可以轻松达到900,000-1,100,000 KB,这是应用程序池的限制。

林没有那么多寻找一个确切的答案,但更像是一个方向,因为我都没有想法。

+1

一种选择是使用临时文件,从这些文件流的结果给客户端...这样内存使用率下降...临时文件可以删除他们一直流 – Yahia

回答

10

将文件数据作为字节数组存入内存时,会给Web服务器带来很大的压力。

除了将整个文件数据存储在字节数组中,您应该尝试将文件流以块的形式写入响应流。

伪例如:

context.Response.Buffer = false; 

byte[] buffer = new byte[4096]; 
int bytesRead = 0; 

using(var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) 
{ 
    while ((bytesRead = stream.Read(buffer, 0 , buffer.Length)) > 0) 
    { 
     context.Response.OutputStream.Write(buffer, 0, buffer.Length); 
     context.Response.OutputStream.Flush(); 
    } 
} 

这里的想法是,你只是把文件的数据块到内存中的文件流的每个读取,然后将它写入响应。请注意,response buffering已被禁用,您可以使用另一个Stream数据源使用文件流进行替换(我在从SQL数据库读取二进制数据时使用了此方法)。

编辑:(响应如何流从SQL到HTTP响应数据)

为了从SQL Server数据库表流数据(例如,VARBINARY(max)列),您使用顺序访问在SqlCommand上:

#region WriteResponse(HttpContext context, Guid id) 
/// <summary> 
/// Writes the content for a media resource with the specified <paramref name="id"/> 
/// to the response stream using the appropriate content type and length. 
/// </summary> 
/// <param name="context">The <see cref="HttpContext"/> to write content to.</param> 
/// <param name="id">The unique identifier assigned to the media resource.</param> 
private static void WriteResponse(HttpContext context, Guid id) 
{ 
    using(var connection = ConnectionFactory.Create()) 
    { 
     using (var command = new SqlCommand("[dbo].[GetResponse]", connection)) 
     { 
      command.CommandType = CommandType.StoredProcedure; 

      command.Parameters.Add("@Id", SqlDbType.UniqueIdentifier); 
      command.Parameters.AddReturnValue(); 

      command.Parameters["@Id"].Value = id; 

      command.Open(); 

      using(var reader = command.ExecuteReader(CommandBehavior.SequentialAccess)) 
      { 
       if(reader.Read()) 
       { 
        WriteResponse(context, reader); 
       } 
      } 
     } 
    } 
} 
#endregion 

#region WriteResponse(HttpContext context, SqlDataReader reader) 
/// <summary> 
/// Writes the content for a media resource to the response stream using the supplied <paramref name="reader"/>. 
/// </summary> 
/// <param name="context">The <see cref="HttpContext"/> to write content to.</param> 
/// <param name="reader">The <see cref="SqlDataReader"/> to extract information from.</param> 
private static void WriteResponse(HttpContext context, SqlDataReader reader) 
{ 
    if (context == null || reader == null) 
    { 
     return; 
    } 

    DateTime expiresOn  = DateTime.UtcNow; 
    string contentType  = String.Empty; 
    long contentLength  = 0; 
    string fileName   = String.Empty; 
    string fileExtension = String.Empty; 

    expiresOn    = reader.GetDateTime(0); 
    fileName    = reader.GetString(1); 
    fileExtension   = reader.GetString(2); 
    contentType    = reader.GetString(3); 
    contentLength   = reader.GetInt64(4); 

    context.Response.AddHeader("Content-Disposition", String.Format(null, "attachment; filename={0}", fileName)); 

    WriteResponse(context, reader, contentType, contentLength); 

    ApplyCachePolicy(context, expiresOn - DateTime.UtcNow); 
} 
#endregion 

#region WriteResponse(HttpContext context, SqlDataReader reader, string contentType, long contentLength) 
/// <summary> 
/// Writes the content for a media resource to the response stream using the 
/// specified reader, content type and content length. 
/// </summary> 
/// <param name="context">The <see cref="HttpContext"/> to write content to.</param> 
/// <param name="reader">The <see cref="SqlDataReader"/> to extract information from.</param> 
/// <param name="contentType">The content type of the media.</param> 
/// <param name="contentLength">The content length of the media.</param> 
private static void WriteResponse(HttpContext context, SqlDataReader reader, string contentType, long contentLength) 
{ 
    if (context == null || reader == null) 
    { 
     return; 
    } 

    int ordinal  = 5; 
    int bufferSize = 4096 * 1024; // 4MB 
    byte[] buffer = new byte[bufferSize]; 
    long value; 
    long dataIndex; 

    context.Response.Buffer   = false; 
    context.Response.ContentType = contentType; 
    context.Response.AppendHeader("content-length", contentLength.ToString()); 

    using (var writer = new BinaryWriter(context.Response.OutputStream)) 
    { 
     dataIndex = 0; 
     value  = reader.GetBytes(ordinal, dataIndex, buffer, 0, bufferSize); 

     while(value == bufferSize) 
     { 
      writer.Write(buffer); 
      writer.Flush(); 

      dataIndex += bufferSize; 
      value  = reader.GetBytes(ordinal, dataIndex, buffer, 0, bufferSize); 
     } 

     writer.Write(buffer, 0, (int)value); 
     writer.Flush(); 
    } 
} 
#endregion 
+0

+1右后:只是想指出使用条款。一旦文件被写入,这确保文件流被正确关闭;这可能是OP的当前问题(还没有看到他们的代码) – NotMe

+0

我想另一个有趣的事情是,有时它会在w3wp.exe过程中得到非常高的接近极限(例如1100MB中的900MB)它仍然能够工作,但是现在它已经达到了540MB,并且正在引发outofmemroyexception。 – atrljoe

+0

@Oppositional如果文件位于数据库中,我将如何指向文件流,因为它需要文件路径的参数。我想我完全不明白。 – atrljoe

1

反对意见对处理文件本身有非常好的建议。我也会看你可能挂在你的网页处理中的参考。在会话状态或应用程序状态中存储任何内容?如果是这样,请仔细追踪这些内容以确保它们不会再指向处理文件所涉及的页面或其他信息。

我提到这个问题,因为几年前我们有一个令人讨厌的“泄漏”,这是因为将对象置于应用程序状态。发现那个对象订阅了页面事件,并且由于对象从未去世,所有的页面都没有!哎呀