Memory ProfileI tested Release 4.0.0. for Memory Leaks with Redgate ANTS Memory Profiler. I have applied fixes to found leaks in Release 4.0.1.
In particular, in the Demo App, I tested:
-
(1) closing a top-level Tab-
(2) closing a User Control within a TabThere had been issues with the AddUserControl and RemoveUserControl Actions keeping alive controls.
For
top-level Tabs, the memory appears now to be 100% clean. If you close a Tab, all objects should be garbage-collected.
EXCEPTION: The only exception to this is Tab 5 which contains a WindowsFormHost and Chart control. I didn't have the time to dig into this deeper as to why this one TabItem5 is being kept alive. I imagine that it is related to either the WindowsFormHost or the Observable.Timer?
For
User Controls within a Tab, I modified the code so that it will always lag garbage-collecting merely a single control. If you open a User Control, then close it, it will NOT immediately be garbage-collected. However, the next time you open a new User Control on that tab, it will release the last one for garbage-collection before loading the new one. This is just because the "laggard" needs to maintain the "AddUserControl" Action until the next User Control is loaded . . .
I believe this is a reasonable mitigation.
Also, there was an issue in the DataGridParent with an object being added to ObservableCollection that failed to implement INotifyPropertyChanged (which apparently is a known cause of memory leaks.) This has been fixed:
public class ColumnContent<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : INotifyPropertyChanged
Global HandlesIn the Tab Control Service, I manually remove any "global" handles stored in
Application.Current.Properties in the Tab Removal code; for example, in Tab Item 10:
//REMOVE ANY HARDCODED GLOBAL REFERENCES
Application.Current.Properties["TabItem10RemoveUserControl"] = null;
Application.Current.Properties["TabItem10AddUserControl"] = null;
Application.Current.Properties["RegionOneAction1"] = null;
Application.Current.Properties["RegionOneAction2"] = null;
In a Production application, you could likely use a DependencyInjection/IoC container such as Unity or Ninject to avoid such issues, or roll your own WeakReference dictionary to prevent global handles keeping Tabs alive? However, in the Demo App, I just took the approach of carefully monitoring these hardcoded references and testing them with the Memory Profiler.