Views/ViewModels don't get unloaded from memory

Topics: Prism v4 - Silverlight 4
Jul 20, 2011 at 5:03 AM

In my SL4, Prism, Unity application, I am having floating-windows that gets loaded in a Region when a menu item is clicked. Everything works fine but when I close the window, the Views and the related ViewModels don't get cleared from memory. This results in my app consuming more and more memory with use. I used Resource Monitor and WinDbg to trace but couldn't get to the bottom of it.

The following is how I am creating the floating window:

 

BaseFloatingWindow window = (BaseFloatingWindow)container.Resolve<IBaseFloatingWindow>();

 

This is how I load/clear view in a region:

 

 public void AddViewInRegion(string regionName, object view)
        {
            regionManager.Regions[regionName].Add(view, (view.ToString()));
            regionManager.Regions[regionName].Activate(view);
           
        }

 public void ClearRegion(string regionName)
        {
            List<object> views = new List<object>();
            foreach (object view in regionManager.Regions[regionName].Views)
            {
                views.Add(view);
            }
            foreach (object view in views)
            {			
				regionManager.Regions[regionName].Remove(view);
				if (((UserControl)view).DataContext is ViewModelBase)
				{
					((ViewModelBase)((UserControl)view).DataContext).KeepAlive = false;
				}
				if (view is ViewBase)
				{
					((ViewBase)view).KeepAlive = false;
				}				
            }
			GC.Collect();
        }

 

And this is how I clear the floating window:

 

		public void BaseFloatingWindow_Closed(object sender, EventArgs e)
		{			
			foreach (object view in this.regionManager.Regions[regionName].Views)
			{
				if (((UserControl)view).DataContext is ViewModelBase)
				{
					((ViewModelBase)((UserControl)view).DataContext).KeepAlive = false;					
				}
				if (view is ViewBase)
				{
					((ViewBase)view).KeepAlive = false;
				}				
			}
			this.Dispose();
			GC.Collect();
		}

 

 

I am taking care to explicitly unsubscribe all event subscriptions (including those related to even aggregator) and also implementing IRegionMemberLifetime on both View and ViewModel classes (base classes actually).

Here is the dump from WinDbg when I close a View (DashboardView) and it still stays in the memory:

0:022> !dumpheap -type Resonant.eEnrol.Silverlight.UI.Modules.Dashboard.DashboardView
 Address       MT     Size
07bb3b14 06e6fd54      136     
total 0 objects
Statistics:
      MT    Count    TotalSize Class Name
06e6fd54        1          136 Resonant.eEnrol.Silverlight.UI.Modules.Dashboard.DashboardView
Total 1 objects
0:022> !GCRoot 07bb3b14
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 6 OSTHread 1478
Scan Thread 18 OSTHread 624
Scan Thread 19 OSTHread 1cbc
DOMAIN(06C49630):HANDLE(Pinned):4f712f4:Root:  08af6260(System.Object[])->
  07b2c6e8(Microsoft.Practices.ServiceLocation.ServiceLocatorProvider)->
  07b1d7d8(Resonant.eEnrol.Silverlight.UI.Bootstrapper)->
  07b1effc(Microsoft.Practices.Unity.UnityContainer)->
  07b1f31c(Microsoft.Practices.ObjectBuilder2.PolicyList)->
  07bf8550(System.Collections.Generic.Dictionary`2[[Microsoft.Practices.ObjectBuilder2.PolicyList+PolicyKey, Microsoft.Practices.Unity.Silverlight],[Microsoft.Practices.ObjectBuilder2.IBuilderPolicy, Microsoft.Practices.Unity.Silverlight]])->
  07bf88a4(System.Collections.Generic.Dictionary`2+Entry[[Microsoft.Practices.ObjectBuilder2.PolicyList+PolicyKey, Microsoft.Practices.Unity.Silverlight],[Microsoft.Practices.ObjectBuilder2.IBuilderPolicy, Microsoft.Practices.Unity.Silverlight]][])->
  07b94bac(Microsoft.Practices.Unity.ContainerControlledLifetimeManager)->
  07ba3ce0(Resonant.eEnrol.Silverlight.UI.Modules.Menu.MenuView)->
  07ba60ac(System.Windows.Controls.ItemsControl)->
  07ba6cb4(System.Windows.Controls.ItemContainerGenerator)->
  07c0a1f0(System.Windows.Controls.ItemContainerGenerator+ItemBlock)->
  07c0a7d4(System.Windows.Controls.ItemContainerGenerator+RealizedItemBlock)->
  07c0a7ec(System.Windows.Controls.ItemContainerGenerator+BlockEntry[])->
  07c0efb8(System.Windows.Controls.ContentPresenter)->
  07c0f0bc(System.Collections.Generic.Dictionary`2[[MS.Internal.IManagedPeerBase, System.Windows],[System.Object, mscorlib]])->
  07c0f108(System.Collections.Generic.Dictionary`2+Entry[[MS.Internal.IManagedPeerBase, System.Windows],[System.Object, mscorlib]][])->
  07c0f2b4(System.Windows.Controls.RadioButton)->
  07c0f498(System.Collections.Generic.Dictionary`2[[System.Windows.DependencyProperty, System.Windows],[System.Windows.EffectiveValueEntry, System.Windows]])->
  07c0fcd4(System.Collections.Generic.Dictionary`2+Entry[[System.Windows.DependencyProperty, System.Windows],[System.Windows.EffectiveValueEntry, System.Windows]][])->
  07c0fd6c(Resonant.eEnrol.Silverlight.Common.RadioButtonCommand+CommandRadioButonBehavior)->
  07c095c8(Microsoft.Practices.Prism.Commands.DelegateCommand`1[[System.String, mscorlib]])->
  07c09634(System.Func`2[[System.Object, mscorlib],[System.Boolean, mscorlib]])->
  07c09604(Microsoft.Practices.Prism.Commands.DelegateCommand`1+<>c__DisplayClass6[[System.String, mscorlib]])->
  07c095a8(System.Action`1[[System.String, mscorlib]])->
  07ba3c2c(Resonant.eEnrol.Silverlight.UI.Modules.Menu.MenuViewModel)->
  07b5191c(Microsoft.Practices.Prism.Regions.RegionManager)->
  07b51928(Microsoft.Practices.Prism.Regions.RegionManager+RegionCollection)->
  07b5193c(System.Collections.Generic.List`1[[Microsoft.Practices.Prism.Regions.IRegion, Microsoft.Practices.Prism]])->
  07b5b328(System.Object[])->
  07bc5dd8(Microsoft.Practices.Prism.Regions.AllActiveRegion)->
  07bc5e08(Microsoft.Practices.Prism.Regions.RegionBehaviorCollection)->
  07bc5e18(System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[Microsoft.Practices.Prism.Regions.IRegionBehavior, Microsoft.Practices.Prism]])->
  07bc625c(System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[Microsoft.Practices.Prism.Regions.IRegionBehavior, Microsoft.Practices.Prism]][])->
  07bc6488(Microsoft.Practices.Prism.Regions.Behaviors.RegionManagerRegistrationBehavior)->
  07bc56a8(System.Windows.Controls.ItemsControl)->
  07bc5a60(System.Windows.Controls.ItemContainerGenerator)->
  07bf2760(System.Windows.Controls.ItemContainerGenerator+ItemBlock)->
  07bff964(System.Windows.Controls.ItemContainerGenerator+RealizedItemBlock)->
  07bff97c(System.Windows.Controls.ItemContainerGenerator+BlockEntry[])->
  07bb3b14(Resonant.eEnrol.Silverlight.UI.Modules.Dashboard.DashboardView)
DOMAIN(06C49630):HANDLE(Pinned):4f712f8:Root:  08af4260(System.Object[])->
  07b41594(Resonant.eEnrol.Silverlight.UI.CoreShell)->
  07b42250(System.Windows.Controls.ItemsControl)->
  07b4ff54(System.Windows.Controls.ItemContainerGenerator)->
  07b92db4(System.Windows.Controls.ItemContainerGenerator+ItemBlock)->
  07bf99b0(System.Windows.Controls.ItemContainerGenerator+RealizedItemBlock)->
  07bf99c8(System.Windows.Controls.ItemContainerGenerator+BlockEntry[])->
  07b8ca24(Resonant.eEnrol.Silverlight.UI.Shell)->
  07b8d110(System.Windows.Controls.ItemsControl)->
  07b90500(System.Windows.Controls.ItemCollection)->
  07b9102c(System.Windows.Controls.WeakCollectionChangedListener)->
  07b90f50(Microsoft.Practices.Prism.Regions.ViewsCollection)->
  07b91488(System.Collections.Specialized.NotifyCollectionChangedEventHandler)->
  07b91180(System.Object[])->
  07b91468(System.Collections.Specialized.NotifyCollectionChangedEventHandler)->
  07b91458(Microsoft.Practices.Prism.Regions.Behaviors.RegionMemberLifetimeBehavior)->
  07b90d7c(Microsoft.Practices.Prism.Regions.AllActiveRegion)->
  07b5191c(Microsoft.Practices.Prism.Regions.RegionManager)->
  07b51928(Microsoft.Practices.Prism.Regions.RegionManager+RegionCollection)->
  07b5193c(System.Collections.Generic.List`1[[Microsoft.Practices.Prism.Regions.IRegion, Microsoft.Practices.Prism]])->
  07b5b328(System.Object[])->
  07bc5dd8(Microsoft.Practices.Prism.Regions.AllActiveRegion)->
  07bc5e08(Microsoft.Practices.Prism.Regions.RegionBehaviorCollection)->
  07bc5e18(System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[Microsoft.Practices.Prism.Regions.IRegionBehavior, Microsoft.Practices.Prism]])->
  07bc625c(System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[Microsoft.Practices.Prism.Regions.IRegionBehavior, Microsoft.Practices.Prism]][])->
  07bc6488(Microsoft.Practices.Prism.Regions.Behaviors.RegionManagerRegistrationBehavior)->
  07bc56a8(System.Windows.Controls.ItemsControl)->
  07bc5a60(System.Windows.Controls.ItemContainerGenerator)->
  07bf2760(System.Windows.Controls.ItemContainerGenerator+ItemBlock)->
  07bff964(System.Windows.Controls.ItemContainerGenerator+RealizedItemBlock)->
  07bff97c(System.Windows.Controls.ItemContainerGenerator+BlockEntry[])->
  07bb3b14(Resonant.eEnrol.Silverlight.UI.Modules.Dashboard.DashboardView)

I have my project here: http://tinyurl.com/eEnrol

I have spent many days now to find what is wrong in my code. I desperately need some help now.

Can someone please have a look at my project above and please, please help me how to avoid this memory leak? Any other suggestion in how I implement Prism is also welcome.

Thanks in advance

Developer
Jul 20, 2011 at 3:01 PM

Hi,

We've examined your solution, and found some things that you could look at to check if they are causing this undesired behavior (note that we couldn't run the solution, since the Resonant.Controls and Resonant.eEnrol.Silverlight.UI.Web projects couldn't be loaded, since there were missing files.)

The GenericMessageServiceLocator, for example, uses the RegisterInstance method. Note that the RegisterInstance method registers your instance in the container using a ContainerControlledLifetimeManager (you can read more about this here), which means that the instance is a singleton instance that will be kept alive as long as the container is kept alive (which is probably as long as you keep your application open). Basically, when you have a memory leak you should check who is holding a reference to your object, and in this case it is likely that in some cases, the container is holding a reference to your view/view model.

We've also noted that you're modifying the KeepAlive property after removing the view. However, the RegionMemberLifetimeBehavior is responsible for determining if an item should be removed from the region when it is deactivated; hence it should have no effect when the region has already been removed from the region.

Finally, we've noted that you placed your modules in the shell project. You should take into account that the usual approach is to place modules into separate projects, so that they can be individually developed, and decoupled from each other.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Jul 20, 2011 at 11:34 PM
Edited Jul 20, 2011 at 11:35 PM

Thank you very much for your reply. I have been able to unload View and ViewModel classes from memory now. However, I am still struggling with BaseFloatingWindow class that holds the Region (and Views).

You should be able to run just the UI project, by right clicking it in VS solution explorer and selecting 'View in browser' (if you have added the service reference files in the project from the other folders). The BaseFloatingWindow class is the class that loads the floating windows. I have used the following library in it: http://www.codeproject.com/KB/silverlight/FloatingWindow.aspx

Thanks again

Developer
Jul 21, 2011 at 2:15 PM
Edited Jul 21, 2011 at 2:16 PM

Hi,

It would be helpful if you could provide us with additional details of the problem that you're experiencing when using BaseFloatingWindow.

Additionally, if you want to use a region in a popup window of any kind, you might find the RegionPopupBehaviors in the StockTrader Reference Implementation useful for this purpose. Based on my understanding on your scenario, it's likely that you could modify the behaviors to use a BaseFloatingWindow instead of a Popup.

You might find the following threads useful, as they are related to the RegionPopupBehaviors:

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Oct 25, 2011 at 10:14 AM

Hi dharmbhav

I have also such problems with MemoryLeaks. Did you find your problem? Perhaps we can work together?

Thank you very much for your feedback.

Thomas

Oct 27, 2011 at 6:44 AM

Hi tkehl,

After much trouble I found that most of the issues are with event registrations. So be sure that you un-register any event before disposing any View/VM that can even remotely be referred in their event handlers.

Moreover, if you are tracking memory leaks with ResourceMonitor or Windbg, don't care too much if the reference stays alive (even after taking necessary steps). I think the way .NET manages memory makes it hard to figure out when the objects are really getting disposed from memory.

Last but not the least, you can refer to the following for understanding how to avoid memory leaks:

http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/learning-memory-management/resources/WPF-Silverlight-Pitfalls.pdf

Thanks,

Dharmesh