WindowManager and possible memory leak

Topics: Bugs
Jun 28, 2011 at 11:42 PM
Edited Jun 28, 2011 at 11:46 PM

I am not sure if this is a memory leak or it's a problem with how I am using the framework. I am using Silverlight 4 and the latest version of Caliburn.Micro, also got the same result using RTW 1.0.

I set up a simple project to see if the problem I was seeing in my main application could be recreated. I have one ViewModel with one method ShowWindow:

        
[Export(typeof(ToolbarViewModel)), PartCreationPolicy(CreationPolicy.NonShared)]
    public class ToolbarViewModel : Screen
    {
        public static IWindowManager DialogManager { get; set; }

        [ImportingConstructor]
        public ToolbarViewModel(IWindowManager dialogManager)
        {
            DialogManager = dialogManager;
        }

        public void ShowWindow()
        {
            var vm = new DialogAViewModel();
            DialogManager.ShowDialog(vm);
        }

    }
DialogAViewModel:
    public class DialogAViewModel : Screen
    {
        public DialogAViewModel() {}
        public void OK() { TryClose(); }
    }
DialogAView:
 
<controls:ChildWindow x:Class="CaliburnTestEnvironment.Views.DialogAView"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                      xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
                      Width="400"
                      Height="300"
                      Title="DialogAView">
    <Grid x:Name="LayoutRoot"
          Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <TextBlock x:Name="Message"
                Text="Hello"
                   Loaded="Message_Loaded"
                   LayoutUpdated="Message_LayoutUpdated"
                Width="75"
                Height="23"/>
        
        <Button x:Name="OK"
                Content="OK"
                Width="75"
                Height="23"
                HorizontalAlignment="Right"
                Margin="0,12,79,0"
                Grid.Row="1" />
    </Grid>
</controls:ChildWindow>
Codebehind for DialogAView:
public partial class DialogAView : ChildWindow
    {
        public DialogAView()
        {
            InitializeComponent();
            this.Closed += new EventHandler(DialogAView_Closed);
            LayoutUpdated += new EventHandler(DialogAView_LayoutUpdated);
        }

        void DialogAView_LayoutUpdated(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("**********\tView layout updated...\t\t{0}", GetHashCode());
        }

        void DialogAView_Closed(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("**********\tView closed...\t{0}", GetHashCode());
        }

        private void Message_Loaded(object sender, RoutedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("----------\tTextblock Loaded...\t\t\t{0}", Message.GetHashCode());
        }

        private void Message_LayoutUpdated(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("----------\tTextblock layout updated...\t{0}", Message.GetHashCode());
        }

    }
 
The result from the debug window after clicking ShowWindow three times and closing the ChildWindow using the OK button:
INFO: Activating CaliburnTestEnvironment.ViewModels.DialogAViewModel.
----------	Textblock layout updated...	36936550
**********	View layout updated...	64479624
----------	Textblock layout updated...	63646052
**********	View layout updated...	27440617
----------	Textblock layout updated...	51921052
**********	View layout updated...	63658128
----------	Textblock Loaded...	51921052
INFO: Action: OK availability update.
INFO: Action: OK availability update.
INFO: Invoking Action: OK.
**********	View closed...		63658128
INFO: Deactivating CaliburnTestEnvironment.ViewModels.DialogAViewModel.
INFO: Closed CaliburnTestEnvironment.ViewModels.DialogAViewModel.
----------	Textblock layout updated...	36936550
**********	View layout updated...	64479624
----------	Textblock layout updated...	63646052
**********	View layout updated...	27440617
----------	Textblock layout updated...	51921052
**********	View layout updated...	63658128
 


As you can see the previous "closed" views are still in memory and somehow in the visual tree even though the Close method was called in the ChildWindow. I am not sure what I am doing wrong.

Jun 29, 2011 at 12:28 AM
Edited Jun 29, 2011 at 8:16 AM

It is not clear to me, if the messages reported after closure are the only ones, or there are more.

In case there are only such messages, there is an high chance that the layout updated events are caused by the fact that binings are 'released'. Once the binding is no more in place, every databound control will revert to the default value and, in case of a TextBlock, it would trigger a layout pass (it seems), which invalidates the parent layout (this is confirmed by the fact that the first LayoutUpdated comes from the TextBlock).

I haven't checked if such layout pass after closing happens just in SL or in WPF too, but I would try to implement the finalizer on the view, call a couple of GC.Collect() and verify if the object is properly reclaimed by the GC.

Jun 29, 2011 at 1:06 AM

Thanks for the response.

I implemented the finalizer in the view and the memory is properly reclaimed by the GC. However, I still don't understand why the LayoutUpdated event is still being raised for Views that were previously closed. From the debug output above the views with hash codes 64479624 and 27440617 somehow are still active even though they do not show up. Only the newly created DialogAView shows up on the screen. What I mean is that these two instances of DialogAView will continue to call layout updated every time I click on ShowWindow. Any ideas?

Jun 29, 2011 at 8:43 AM

Could you please provide the source code for this sample?

As far as I understand, you use a toolbar item to display a new dialog, and you keep on creating and closing new dialogs; moreover, in your repro-project layout updated events are raised for dialogs that are previously closed, until next garbage collection. Am I correct? I'm trying to figure how CM could be related to such behaviour, but honestly, I doubt that this is caused by the framework... LayoutUpdated events are raised as a consequence of calling InvalidateArrange, and such action is tipically handled by the WPF/SL engine (this could be a typical effect of using WeakEvents, that are kept alive until the control is garbage collected). Is it possible that this behaviour is related to the ChildWindow itself? You could try to reproduce the same scenario without CM and see what happens.

Jun 29, 2011 at 6:34 PM

You are right. I tried it without the framework. Simple form with a button to create the child window in code behind. Same behavior, so it has nothing to do with the framework. Thanks for your input.