简单研究一下asp.net上传文件时的内存占用问题

最近看见有人在研究asp.net的上传组件,如何上传大文件的问题,于是简单地研究了一下,发现了一些东西,总结如下,希望大家能够提出一个较好的解决方案。

据msdn所言,在Web.Config中,默认可以上传的文件大小为8M,如果需要上传更大的文件,则需要手动指定: 这里的示例是最大100M的,经过测试,100M以下的文件还是可以正常上传的(占用内存必须在服务器可用内存60%以下)在使用.NET中的文件上传的功能时,实际上使用的是Html控件,并不是Web控件,这就意味着,HtmlInputFile控件不是明显地支持服务端的Event的,所以,提交时,应该使用Html控件的Button进行Submit。

经测试,可以发现,当不断地进行上传文件的时候,内存的消耗是不断地累积的,也就是说,当你上传了一个20M的文件后,再上传20M时,缓存中内存消耗会达到40M左右。 在MSDN文档中的说明里面指出,当文件上传到服务器后,是存储在缓存中的,在没有把缓存中的数据存储成文件时,是不会清空缓存的。

其实,这句话有一定的误导性,因为在HtmlInputFile控件中,PostedFile属性中,提供了一个SaveAs方法成员,给许多人一种错觉就是,当进行了HtmlInpuFile的SaveAs后,缓存会被清空。实际上不是这样,首先,查看一下HtmlInputFile中的PostedFile属性的代码,如下:

[WebCategory("Default"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue("")]
public HttpPostedFile PostedFile
{
 get { return this.Context.Request.Files[this.RenderedNameAttribute]; }
}
可以看出,实际上是一个HttpPostedFile类实例,那么要明白它的工作原理,肯定就要追到HttpPostedFile类去看看。

好了,我们再看看,这个HttpPostFile中提供的SaveAs()的代码:
public void SaveAs(string filename)
{
 HttpRuntimeSection section1;
 if (!Path.IsPathRooted(filename))
  {
  section1 = RuntimeConfig.GetConfig().HttpRuntime;
  if (section1.RequireRootedSaveAsPath)
  {
    throw new HttpException(HttpRuntime.FormatResourceString("SaveAs_requires_rooted_path", filename));
  }
  }
  FileStream stream1 = new FileStream(filename, FileMode.Create);
  try
  { 
  this._stream.WriteTo(stream1);
  stream1.Flush();
  return;
  }
  finally
  {
  stream1.Close();
  }
}

注意,用标记的那一句,观看整个代码,可以发现的是:实际上,在SavaAs方法中,处理的流是在此方法中新建的一个FileStream的实例,这里只有stream1.Flush(),而this._stream所指向的实例实际上还存在于缓存中,它才是真正占用了大量内存空间的罪人。

好了,再看看HttpPostedFile的构造函数,就可以知道this._stream是从哪里来的了.
internal HttpPostedFile(string filename, string contentType, HttpInputStream stream)
 {
  this._filename = filename;
  this._contentType = contentType;
  this._stream = stream;
 }
嗯?原来如此,这里的流对象原来也只是一个引用罢了,真正的占用内存的杀手在哪儿?看样子,只要知道了是谁创建了HttpPostedFile这个类的实例,谁就是罪魁祸首。

不过,这里,我们要暂放一步,目前我们基本知道了实际上进行SaveAs方法后,缓存中的数据并没有被清除,相反的,它还好好地留在内存中。 我们的目标是要解决这个问题,正好,在HtmlInputFile中也给出了所创建的实例的引用,所以,现在,针对HttpInputStream流来进行一些处理。

保险起见,我们先看看HttpInputStream的内容吧,再作定论吧。

public override void Flush() { }
@#%*&@,Flush方法居然是这样写的,嗯,以后要注意了!

public override void Close() { this.Uninit(); }
这是Close方法,里面调用了Uninit(),所以,我们再看看Uninit.

protected void Uninit() { this._data = null; this._offset = 0; this._length = 0; this._pos = 0; }
Mmmm,虽然感觉有点不爽,但起码使用这个方法时,这个方法能够保证它会把缓存对象清空。

最后,写出的代码类似下面的形式:
private void btnUpLoad_ServerClick(object sender, System.EventArgs e)

 try
 {
   string strFileName = new FileInfo(imageUpload.PostedFile.FileName).Name;
   UploadFile.PostedFile.SaveAs("C:\\"+strFileName);
   Response.Write("上传成功!");
 }
 catch
 {
  Response.Write("上传失败!");
 }
 finally
 { 
   HttpInputStream upStream = imageUpload.PostedFile.InputStream;
   upStream.Close();
  }
}

经过测试,发现这样也不是一个理想的方案,还必须进一步补充才行。
过程如下:
在本机上,浏览FileUpload.aspx,此时,w3wp进程占用内存空间为28,948K
当上传一个12.7M的文件后,据返回的信息表示,上传的文件大小为133307341,内存占用变化为49,312K。
再上传一个一个371K的文件,据返回的信息表示,上传的文件上小为3799041,内存占用此时为31,540K

似乎达到了我们的要求,但这里有一个问题就是,当你上传一个大文件后,没有再上传一个小文件的话,那么这个大文件将会在内存里一直占用空间,不知,是否还有更好的办法去解决,现在快1点了,睡觉,以后再说。

posted @ 2004-08-09 00:41 本园第一神棍 阅读(2663) 评论(9)  编辑 收藏

  回复  引用  查看    
#1楼 2004-08-09 09:09 | bestcomy      
此文对于理解上传的整个过程很具有借鉴性
  回复  引用  查看    
#2楼 2004-08-09 14:53 | wayfarer      
如果对缓存的清除必须等待下一个任务的进入,实在是不爽。莫非Asp.net没有提供方法直接干预缓存的清除吗?是否和GC有关系。由于文件是非托管对象,难道必须实现IDisposable,然后调用Dispose方法来清除。

具体情况我不太了解,希望寒枫天伤能研究出结果来。
  回复  引用  查看    
#3楼 2004-08-09 19:30 | 寒枫天伤      
实际上这样的,在web.config中的配置,如果设定的executionTimeout时间比较长的话,那么,连续上载多个文件时,内存消耗还是可观的。
在Asp.net2.0中,这方面的处理方式没有改变。

  回复  引用  查看    
#4楼 2004-08-11 20:42 | 寒枫天伤      
@wayfarer:

仔细研究一下,就会发现,没有提供方法直接干预缓存的清除。
一般性的处理,只有等待executionTimeout的设定时间了。

executionTimeout时间不能设得太短,如果设的时间少于系统处理时间,会延慢垃圾回收的时间,并且此时的回收是不确定的,一般情况下,必须等到下一次的响应,系统才会进行以前的对象回收。
当然,时间尽量设长,对多人同时上传时,服务器的负担会更重,所以有必须改变服务器的处理方式。

目前正在研究中........
  回复  引用  查看    
#5楼 2004-09-11 19:54 | NoRax      
512M内存的机子运行ASP.NET网站 当w3wp.exe进程占用内存到52,000K的时候,JIT开始垃圾回收.
  回复  引用    
#6楼 2006-03-20 11:06 | hfghfghfg [未注册用户]
我用的 上传 方法
内存使用时 32 k
我测试过100兆以上的 文件
procedure TWebModule_UP.WebModule1WebActionItem_UpAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
const
BuffSize = 32 * 1024;
var
i, FilebeginIdx, NowFileSize, FileSize, B, S, j, HeadEndIdx, FileEndIdx: integer;
strFileName: string;
Fs: TFileStream;
BytesRead, nowRead: integer;
Head, Buffer: string;
sDelimiter: string;
path: string;
input_code, uP: string;
sl: TStringList;
  回复  引用    
#7楼 2006-03-20 11:06 | hfghfghfg [未注册用户]
procedure checkHead;
var
B1, b2: integer;
s: string;
begin
if FilebeginIdx < 1 then
begin
if HeadEndIdx < 1 then
begin
B1 := pos(#13#10#13#10, Buffer);
if b1 >= 0 then
begin
s := copy(Buffer, B1 + 4, 1024 * 2);
b2 := pos(#13#10#13#10, s);
if b2 > 0 then
HeadEndIdx := b1 + b2 + 3;
end;
end;
if HeadEndIdx > 0 then
begin

FilebeginIdx := HeadEndIdx + 4;
Head := copy(Buffer, 1, HeadEndIdx - 1);
b1 := pos('name="input_code"', Head);
s := trim(copy(Head, b1 + length('name="input_code"'), 255));
b1 := pos(#13#10, s);
if b1 > 0 then
input_code := trim(copy(s, 1, b1));

j := pos(#13#10, Head);
sDelimiter := copy(Head, 1, j - 1);
FileEndIdx := FileEndIdx - (145 - 2 * 43) - 2 * length(sDelimiter);
FileSize := FileEndIdx - FilebeginIdx + 1;
j := Pos('filename="', Head);

if j > 0 then
begin
strFileName := Copy(Head, j + 10, MaxInt);
strFileName := ExtractFileName(Copy(strFileName, 1, Pos('"', strFileName) - 1));
strFileName := StringReplace(strFileName, ' ', '', [rfReplaceAll]);

path := ExtractFilePath(Request.TranslateURI(Request.URL)) + 'File\' + input_code + '\';
if not DirectoryExists(path) then
begin
ForceDirectories(path);
end;
strFileName := path + strFileName;

fs := TFileStream.Create(strFileName, fmShareDenyNone or fmCreate);
end
else
;
end;
end;

end;
procedure w;
begin
if FilebeginIdx > 0 then
begin

if FilebeginIdx > BytesRead then
B := FilebeginIdx
else
B := 1;
S := nowRead - b + 1;
if s + NowFileSize > FileSize then
s := FileSize - NowFileSize;
if strFileName <> '' then
fs.Write(Buffer[B], S);
NowFileSize := NowFileSize + s;
end;
end;
  回复  引用    
#8楼 2006-03-20 11:07 | hfghfghfg [未注册用户]
begin
try
inc(AccessCount);

Response.ContentType := 'text/html';

try
FilebeginIdx := 0;
BytesRead := 0;
HeadEndIdx := 0;
NowFileSize := 0;
strFileName := '';
FileEndIdx := Request.ContentLength;
FileSize := 0;
{sl := TStringList.Create;
sl.Text := Request.Content;
sl.SaveToFile('d:\1.txt');
FreeAndNil(sl); }

Buffer := Request.Content;
nowRead := length(Buffer);
checkHead;
w;
BytesRead := nowRead;

if FileSize > NowFileSize then
begin
setlength(Buffer, BuffSize);
try
repeat
nowRead := FileSize - NowFileSize;
if nowRead > BuffSize then
nowRead := BuffSize;
nowRead := Request.ReadClient(Buffer[1], BuffSize);
checkHead;
if nowRead > 0 then
w;
BytesRead := BytesRead + nowRead;
until
(nowRead <= 0) or (NowFileSize >= FileSize);
except
;
end;

end;
Buffer := '';
if strFileName <> '' then
try
freeandnil(fs);
except;
end;
uP := lastD(Request.URL);
if strFileName <> '' then
uP := uP + '/up_suc.asp'
else
uP := uP + '/up_error.asp';
Response.SendRedirect(uP);

except

on E: Exception do
begin
uP := lastD(Request.URL);
uP := uP + '/up_error.asp';
Response.SendRedirect(uP);
end;

end;

Buffer := '';
except
on E: Exception do recordEvent('up ' + strFileName + ' ' + E.Message);
end;

end;


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2004-08-09 01:07 编辑过
Google站内搜索


相关链接:
 



Web Counter