Why does my memory usage blow up after stopping a download?
My program downloads mp3 files anywhere from 5MB to 100MB in size, and the memory "leak" size is the size of the download. The memory usage starts gradually going up after I click the button that pauses or cancels the download. That extra memory nukes itself after it finishes accumulating [filesize] bytes of memory, but I can't figure out what's causing it or how to stop it.
When I skip/cancel the download, I change a state that it checks for so that it exi开发者_Go百科ts the download loop and disposes the stream from HttpWebResponse.GetResponseStream.
Then the memory usage gradually climbs up, for example for a 20MB download it will climb 20MB (doesn't matter if I paused it halfway through the download). It climbs at about the rate it takes for the file to download from the internet. When it hits that amount, it plateaus there if the download has not been resumed from the pause (if I canceled the download, it goes back down immediately after reaching that point). When I hit resume on the download, the download restarts itself and as soon as it starts getting data again, the memory usage drops immediately back down to normal. However, either way, it will not get rid of the leak until it has reached that top point.
I've tried explicitly disposing everything involved, including the httpwebresponse, and it didn't change anything. It seems like something's not being closed, or something's keeping a reference of the stream that's making it accumulate and store stuff in memory while it gets ignored..? I'm not sure where to go with this, what else can give me clues. I wish there was like a resource monitor that'd tell me what kind of content is hogging all all the memory. Any help is appreciated.
Here's the code I'm using, as full as I can keep it without too much clutter:
public void Download()
{
while( paused )
{
Thread.Sleep( 1000 );
}
if( canceled )
// signal end of download
else
{
// Checking, rechecking, if/else/etc. In short, this:
Enabled = true;
var request = (HttpWebRequest)WebRequest.Create( url );
request.AllowReadStreamBuffering = false;
if( m_bytesCurrentlyDownloaded != 0 )
{
request.Headers["Range"] = string.Format( "bytes={0}-{1}",
m_bytesCurrentlyDownloaded.ToString(),
Filesize - 1 );
}
request.BeginGetResponse( new AsyncCallback( DownloadCallback ), request );
}
}
private void DownloadCallback(IAsyncResult result)
{
var request = (HttpWebRequest)result.AsyncState;
HttpWebResponse response;
try
{
response = (HttpWebResponse)request.EndGetResponse( result );
}
catch( WebException e )
{
response = (HttpWebResponse)e.Response;
}
if( response.StatusCode == HttpStatusCode.OK ||
response.StatusCode == HttpStatusCode.PartialContent )
{
FileMode openMode = (m_bytesCurrentlyDownloaded == 0) ? FileMode.OpenOrCreate : FileMode.Append;
using( var stream = response.GetResponseStream() )
using( var appStorage = IsolatedStorageFile.GetUserStoreForApplication() )
using( var file = new IsolatedStorageFileStream( m_incompleteFilename, openMode, FileAccess.Write, appStorage ) )
{
byte[] chunk = new byte[chunkSize];
int readLength = 0;
do
{
if( paused || canceled )
readLength = 0;
else
{
readLength = stream.Read( chunk, 0, chunkSize );
if( readLength != 0 )
{
file.Write( chunk, 0, readLength );
m_bytesCurrentlyDownloaded = (int)file.Length;
}
}
} while( readLength != 0 );
chunk = null;
}
if( m_bytesCurrentlyDownloaded < Filesize ) // got paused, or connection drop
{
NeedWaitForNetwork(); // waits only if it needs to
Download();
}
else
FinishDownload();
}
}
This helped me think about it: http://msdn.microsoft.com/en-us/magazine/cc163491.aspx Sounded like a stack leak to me. Threads make stacks. Always felt like a thread that was out of my control that was accumulating all that memory, suspicious of maybe some thread belonging to HttpWebRequest/Response.
This helped me too: http://www.geekpedia.com/tutorial179_Creating-a-download-manager-in-Csharp.html He's doing a similar thing, and when he stops a download, he outright aborts the download thread that he makes. That .Abort() call rang a bell and I looked into HttpWebRequest.Abort().
Learning about ManualResetEvent yesterday helped, too. So now my code has this added:
Added a member to my downloader class:
private ManualResetEvent downloadBreakoutEvent = new ManualResetEvent( false );
Inside Download(), instead of just the BeginGetResponse line,
downloadBreakoutEvent.Reset();
request.BeginGetResponse( new AsyncCallback( DownloadCallback ), request );
downloadBreakoutEvent.WaitOne();
if( canceled || paused )
{
request.Abort();
}
And at the very bottom of DownloadCallback(),
downloadBreakoutEvent.Set();
I pause/resume it over and over, cancel and restart over and over, whatever, no memory leaks!
精彩评论