I have a WindowsPhone 7.8 application, that needs to display animated GIFs as well as images that are supported directly by Silverlight. Therefor we created a custom user control public sealed partial class ExtendedImageControl : UserControl, IDisposable
, which has a DependancyProperty called ImageSource. This property is bound to a URL. The code behind will then either insert a regular Image control into the LayoutRoot, or a AnimatedImage. However, the AnimatedImage will not release its memory when it gets out of view or the containing page is closed.
The loading logic is the following:
ExtendedImage loadedImage = (ExtendedImage)await Utils.Caching.Image.LoadCachedImageFromUrlAsync<ExtendedImage>(loadedLocation);
if (ImageSource == loadedLocation)
{
AnimatedImage image = new AnimatedImage();
image.Stretch = stretch;
image.Source = loadedImage;
LayoutRoot.Children.Add(image);
CurrentImageMode = ExtendedImageMode.AnimatedImage;
loadedImage = null;
#if DEBUG
App.logger.log("Loaded {0} as animated image", loadedLocation);
#endif
imageDisplay = image;
raiseImageUpdated();
}
Ultimately, the image is loaded with
WebClient client = new WebClient();
ExtendedImage image = new ExtendedImage();
using (Stream source = await client.OpenReadTaskAsync(location))
{
if (location.ToString().EndsWith("gif", StringComparison.InvariantCultureIgnoreCase))
{
image.SetSource(source);
TaskCompletionSource<ExtendedImage> imageLoaded = new TaskCompletionSource<ExtendedImage>();
EventHandler loadingCompleteHandler = new EventHandler((sender, e) =>
{
imageLoaded.SetResult(image);
});
EventHandler<UnhandledExceptionEventArgs> loadingFailedHandler = new EventHandler<UnhandledExceptionEventArgs>((sender, e) =>
{
imageLoaded.SetResult(image);
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
System.Diagnostics.Debugger.Break();
#endif
});
image.LoadingCompleted += loadingCompleteHandler;
image.LoadingFailed += loadingFailedHandler;
image = await imageLoaded.Task;
//Remove handlers, otherwise the object might be kept in the memory
image.LoadingCompleted -= loadingCompleteHandler;
image.LoadingFailed -= loadingFailedHandler;
}
else
{
//... load with native Silverlight methods
}
}
return image;
We noticed the memory problem very early on, so the control implements the IDisposable interface with
public void Dispose()
{
unloadImage();
GC.SuppressFinalize(this);
}
private void unloadImage()
{
SmartDispatcher.BeginInvoke(() =>
{
if (imageDisplay != null)
{
if (imageDisplay is AnimatedImage)
{
if ((imageDisplay as AnimatedImage).Source != null & (imageDisplay as AnimatedImage).Source.Frames != null)
(imageDisplay as AnimatedImage).Source.Frames.Clear();
(imageDisplay as AnimatedImage).Stop();
(imageDisplay as AnimatedImage).Source = null;
}
else if (imageDisplay is Image && ((Image)imageDisplay).Source != GIFplaceholder)
{
(imageDisplay as Image).Source = null;
}
imageDisplay = null;
}
});
}
However, the Dispose method is never called on the image.
What can I do to find out why this object isn't picked up by the GC? I'm not registering any eventhandlers so as far as I understand, it should be collected when the Application page is navigated away from. I tried adding a destructor as well, however this one is not called as well.
The memory leak is not in ImageTools but is actually a bug in the Silverlight Runtime:
Memory Leak when you Dynamically add and remove Images
Workaround: When dynamically adding or removing BitmapImages from an application (a.k.a. adding/removing from the tree), you should set Image.Source = null before removing the Image element from the tree. This will make the BitmapImage eligible for garbage collection. Bug Status: Active bug. *
References:
Silverlight: How to unload (dispose) an image from memory? https://blogs.msdn.microsoft.com/silverlight_sdk/2008/10/28/silverlight-bugs-and-workarounds/
One can quickly verify that setting each ExtendedImage to Nothing before back navigating away from the XAML page can result in dramatic reduction in application memory use.
I add the following to my RootFrame_Navigating event in App.xaml to help track these leaks:
Dim applicationCurrentMemoryUsage As Long = Microsoft.Phone.Info.DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage")
Dim applicationPeakMemoryUsage As Long = Microsoft.Phone.Info.DeviceExtendedProperties.GetValue("ApplicationPeakMemoryUsage")
Debug.WriteLine(DateTime.Now.ToLongTimeString() & " Current : " & applicationCurrentMemoryUsage & " Peak : " & applicationPeakMemoryUsage)