WPF & Prism 4.1 Garbage Collection / Memory Issues

Topics: Prism v4 - WPF 4
Feb 24, 2013 at 7:52 PM
Edited Feb 24, 2013 at 8:32 PM
I built a Prism application using WPF, .Net 4, Prism 4.1, and Unity. I'm using a DirectoryModuleCatalog to find modules at runtime. My views are displayed in a TabControl (MainRegion). When I remove a view from the region, the view and viewmodel remain in memory and never get garbage collected - the tabitem is removed. After many hours searching, I cannot figure out what I'm doing wrong.

Here's my bootstrapper:
public class Bootstrapper : UnityBootstrapper
{
    protected override void InitializeShell()
    {
        base.InitializeShell();
        App.Current.MainWindow = (Window)Shell;
        App.Current.MainWindow.Show();
    }

    protected override DependencyObject CreateShell()
    {
        var shell = new Shell();
        return shell;
    }

    protected override IModuleCatalog CreateModuleCatalog()
    {
        return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
    }
}
Here's my module:
[Module(ModuleName = "ModuleA")]
public class Module : IModule
{
    private IRegionManager _regionManager;

    public Module(IRegionManager regionManager)
    {
        _regionManager = regionManager;
    }

    public void Initialize()
    {
        var view = new UserControl1();
        //_regionManager.RegisterViewWithRegion("MainRegion", typeof(UserControl1));
        _regionManager.Regions["MainRegion"].Add(view, "ModuleA");
        _regionManager.Regions["MainRegion"].Activate(view);
    }
}
And heres the viewmodel for my view that gets added to the region:
public class ViewModel
{
    public DelegateCommand RemoveView { get; set; }

    public ViewModel()
    {
        RemoveView = new DelegateCommand(() =>
            {
                var regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
                var view = regionManager.Regions["MainRegion"].GetView("ModuleA");
                regionManager.Regions["MainRegion"].Deactivate(view);
                regionManager.Regions["MainRegion"].Remove(view);
            });
    }
}
And here's the code behind for the view:
public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();

        this.DataContext = new ViewModel();
    }
}
I've read that it could be because I'm instantiating the view in the module or perhaps the viewmodel in the view? When I use Red Gate Memory Profiler, and remove the view via the DelegateCommand, the view and viewmodel are both flagged as not being able to be garbage collected due to a weak reference. Where is the reference that I'm not properly cutting?

Here's teh retention graph for the view model link
Developer
Feb 25, 2013 at 9:08 PM
Hi,

So far I couldn't find the cause of this problem reviewing the code snippets you posted above. Also, I am unable to see the graph you linked below (I just sent you a access request for it).

Based on my understanding, the action of simply instantiating the view in the module (like in the code snippet) would not cause a memory leak. Also, as far as I know, weak references should not prevent an object from being garbage collected, so this might not be the cause behind it (although, I am not seeing anything that might create a weak reference in the code snippets either...)

It would be helpful if you could provide us with more information about your scenario so that we can help you further with this. For example, it would be useful to know what data bindings are being done against the RemoveView property and where. Also, I believe it could be worth checking if this issue is also present when using a different type of region, for example an ItemsControl or ListBox .

Thanks,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Feb 27, 2013 at 3:17 AM
Edited Feb 27, 2013 at 3:25 AM
Hi Damian,

I got a little further in my discovery process of the actual issue.

Our solution is structured like so:
  • Infrastructure
  • Shell (main app project)
  • TabControl module
  • ModuleA
  • ModuleB
  • ModuleC
  • Menu module
TabControl module, Menu module, ModuleA, B, and C are loaded with the directory module catalog.

The IModule in Module A, B, and C sends an EventAggregator message to the menu module to create the menu and via a CompositeCommand tell the menuitem what views to add to the TabControl region when clicked. After the views are added to the TabControl region, sub controls are displayed via a view-model first approach defined in each modules ResourceDictionary (DataTemplates).

I think part of our problem is mixing viewmodel-first with view first. When we attempt to remove views from the TabControl region (via the RegionManager), the view is actually removed from the TabControl but the associated Views and ViewModels linger around in memory - still not exactly sure why, but during the removal of the views, if I set view.DataContext = null it seems to help.

One really weird thing that was happening is if I added a new view to the tab region, and did nothing to the UI contained within that view (including focus on controls), then removed the veiw from the region it would get garbage collected. The instant any sort of interaction was done on the view it wouldn't garbage collect after being removed from the region. I created a test solution with only one module. The view in the module only contained 1 TextBox with proper binding to the viewmodel. As soon as I focused on that textbox and then removed the view from the region, both the view and viewmodel stayed in memory.

Tomorrow I will attempt to alter the program so that when each module loads, it will send an event to the main application which will merge resource dictionaries and then just pass the viewmodel to the region for a full viewmodel-first approach.

Thanks
Feb 27, 2013 at 3:08 PM
Here's the link to one of my test solutions. Click the close button and notice that the View and ViewModel remain in memory.


The list in my modules viewmodel generates 16mb worth of list<string> data just for me to easily see the change in Ants memory profiler. The 16mb remains in memory.

I'm at wits end with this thing :(
Developer
Feb 27, 2013 at 8:42 PM
Hi,

Thanks you for provide us with a repro-sample application. Unfortunately, we couldn't download it as we need you to grant us access to it (I just sent you an access request.) If it's not possible to share the solution in that hosting service, it would be useful if you could upload it to another file hosting service of your preference so that we could download it.

On the other hand, based on your last description it seems that you are using scoped regions. If that is the case, the problem you are experiencing could be related to a known issue in Prism where if a parent view containing regions is removed, its child views are kept alive causing a possible memory leak:
Regards,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Feb 27, 2013 at 9:03 PM
Edited Feb 27, 2013 at 9:18 PM
Hi Damian,

Try this link

I read that link as well a couple days ago, but believe that it does not apply since the views being removed do not have nested regions. I also tried RegionBehaviors:ClearChildViewsRegionBehavior just to be sure and get the same result.

It's like this:
  • MainRegion (ContentControl)
    • MainContentRegion (TabControl loaded in MainRegion)
      • UserControl1 (tab item 1 - datacontext is viewmodel1)
        • ContentControl (viewmodel2 - property in viewmodel1, displayed via datatemplate)
          • ContentControl (viewmodel3 - property in viewmodel2, displayed via datatemplate)
When I try to remove UserControl1 from the MainContentRegion it stays in memory, there are no subregions below MainContentRegion.

Let me know if you are able to grab the file from the link above.

Thanks,
Chris
Feb 27, 2013 at 9:05 PM
Edited Feb 27, 2013 at 9:06 PM
Duplicate post :(
Feb 27, 2013 at 9:05 PM
Edited Feb 27, 2013 at 9:06 PM
Duplicate post.
Feb 28, 2013 at 4:22 AM
I think the memory leak may actually be due to incorrect binding mode or binding to objects that are not INotifyPropertyChanged / Dependency properties, etc.

Will verify in the morning.
Feb 28, 2013 at 7:22 PM
Finally found the root cause of my problem.... not exactly sure why it was messing things up though.

In our Shell.xaml we were binding "IsDefault" in one of our buttons to a PasswordBox's IsKeyboardFocused:
<Button Style="{DynamicResource RedSubmitButtonStyle}" IsDefault="{Binding ElementName=passwordBox1, Path=IsKeyboardFocused}" Command="{Binding LoginCommand}" Content="Login" Height="23" HorizontalAlignment="Left" Margin="145,86,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
IsKeyboardFocused, is a dependency property according to MSDN

Perhaps its related to the PasswordBox attached property we're using to bind to the value entered into the PasswordBox - I found the code for that on another site.

Here's the full contents of our Shell.xaml file
<Grid x:Name="MainGrid" core:SharedResourceDictionary.MergedDictionaries="TabControlThemes;MenuThemes;ButtonThemes;DataGridThemes;TreeViewThemes;ComboBoxThemes;ListBoxThemes;GroupBoxThemes;ToggleSwitchThemes">
        
    <DockPanel>
        <ContentControl x:Name="menuContent" DockPanel.Dock="Top" prism:RegionManager.RegionName="MenuRegion" />
        <ContentControl DockPanel.Dock="Bottom" prism:RegionManager.RegionName="FooterRegion" />
        <ContentControl DockPanel.Dock="Top" prism:RegionManager.RegionName="MainContentRegion" />
    </DockPanel>

    <StackPanel Orientation="Horizontal" Panel.ZIndex="4" HorizontalAlignment="Right" VerticalAlignment="Top">
        <Button Visibility="{Binding IsFullScreenToggleVisible, Converter={StaticResource visConv}}" Command="{Binding ToggleFullScreen}" Height="50" Name="button4" Width="70" HorizontalAlignment="Right" Margin="0,10,10,0" VerticalAlignment="Top">
            <Button.Content>
                <TextBlock FontSize="12" FontWeight="Bold" TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" Text=" Toggle Full Screen" />
            </Button.Content>
        </Button>

        <Button Visibility="{Binding IsAppCloseButtonVisible, Converter={StaticResource visConv}}" Command="{Binding ShutdownApplication}" Height="50" Name="button3" Width="50" HorizontalAlignment="Right" Margin="0,10,10,0" VerticalAlignment="Top">
            <Button.Content>
                <Image Source="..\Graphics\close.png" Name="image1" />
            </Button.Content>
        </Button>
    </StackPanel>

    <xctk:ChildWindow Name="loginChildWindow" Panel.ZIndex="3" CloseButtonVisibility="Collapsed" FocusedElement="{Binding ElementName=usernameTextBox}" WindowStartupLocation="Center" WindowState="{Binding IsVisible, Mode=TwoWay, Converter={StaticResource boolConverter}}" IsModal="True" OverlayOpacity="1" Caption="Pioneer Login" Height="164" Width="261">
        <xctk:ChildWindow.OverlayBrush>
            <ImageBrush Stretch="None" Viewport="0,0,46,29" ViewportUnits="Absolute" ImageSource="../Graphics/escheresque.png" TileMode="Tile" />
        </xctk:ChildWindow.OverlayBrush>
        <xctk:BusyIndicator IsBusy="{Binding IsLoginBusy}" BusyContent="Authenticating...">
            <Grid>
                <TextBox GotFocus="usernameTextBox_GotFocus" Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="99,20,0,0" Name="usernameTextBox" VerticalAlignment="Top" Width="120">
                    <TextBox.InputBindings>
                        <KeyBinding Key="Enter" Command="{Binding LoginCommand}" />
                    </TextBox.InputBindings>
                </TextBox>
                <Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="28,18,0,0" Name="label1" VerticalAlignment="Top" />
                <Label Content="Password" Height="28" HorizontalAlignment="Left" Margin="29,47,0,0" Name="label2" VerticalAlignment="Top" />
                <PasswordBox attach:PasswordBoxAssistant.BindPassword="True" attach:PasswordBoxAssistant.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="100,50,0,0" Name="passwordBox1" VerticalAlignment="Top" Width="120" />
                <Button Style="{DynamicResource RedSubmitButtonStyle}" IsDefault="{Binding ElementName=passwordBox1, Path=IsKeyboardFocused}" Command="{Binding LoginCommand}" Content="Login" Height="23" HorizontalAlignment="Left" Margin="145,86,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
                <Button Style="{DynamicResource RedSubmitButtonStyle}" Command="{Binding LazyLoginCommand}" Visibility="{Binding IsDebugMode, Converter={StaticResource visConv}}" Content="Quick Login" Height="23" HorizontalAlignment="Left" Margin="23,87,0,0" Name="button2" VerticalAlignment="Top" Width="89" />
            </Grid>
        </xctk:BusyIndicator>
    </xctk:ChildWindow>

</Grid>
Developer
Feb 28, 2013 at 8:34 PM
Hi,

I was checking your test application but I was unable to find a solution for it so far; so I am glad to hear that you could found the cause behind this problem!

Regards,

Damian Cherubini
http://blogs.southworks.net/dcherubini