Silverlight + MVVM + Bindings = Memory leaks?
Asked Answered
H

2

9

Thus far, my testing has shown that all standard approaches, examples, and frameworks leveraging the MVVM pattern in silverlight suffer from a huge problem: massive memory leaks which prevent VMs from being garbage collected.

Obviously this is a huge and ridiculous claim - so my expectation is that someone will have an obvious answer of why and where I'm going wrong :)

The steps to reproduce are simple:

  • Bind your viewmodel to a view by setting the views datacontext to the VM (assume the viewmodel leverages INotifyPropertyChanged to support data binding)
  • Bind a UI element to a property on the viewmodel, for example:

<TextBox Text="{Binding SomeText}" />

  • Leverage the binding in some way (for example - just type in the textbox).

This creates a reference chain that extends from the root, to a BindingExpression, to your viewmodel. You can then remove the View from your UI tree, as well as all refernences to the VM - however the VM will never be garbage collected thanks to the root<>BindingExpression<>VM reference chain.

I have created two examples illustrating the problem. They have a button to create a new view/viewmodel (which should dump all references to the old one(s)) and a button which forces garbage collection and reports on the current memory usage.

Example 1 is a super stripped down caliburn micro example. Example 2 uses no frameworks and just illustrates the problem in the simplest way I could think of.

Example 1

Example 2

For those who might like to help but don't wish to download the example projects, here is the code for example 2. We start with a viewmodel called FooViewModel:

 public class FooViewModel : INotifyPropertyChanged
{
    string _fooText;

    public string FooText
    {
        get { return _fooText; }
        set
        {
            _fooText = value;
            NotifyPropertyChanged("FooText");
        }
    }

    private byte[] _data;
    public FooViewModel()
    {
        _data = new byte[10485760]; //use up 10mb of memory
    }



    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

It simply exposes a string property called FooText which we will bind too. INotifyPropertyChanged is neccessary to facilitate the binding.

Then we have a view called FooView which is a usercontrol containing:

<UserControl x:Class="MVVMLeak.FooView">
    <StackPanel x:Name="LayoutRoot" Orientation="Horizontal">
        <TextBlock Text="Bound textbox: " />
        <TextBox Text="{Binding FooText}" Width="100"/>
    </StackPanel>
</UserControl>

(namespaces omitted for brevity)

The important bit here is the textbox which is bound to the FooText property. Of course we need to set the datacontext, which I've chosen to do in the codebehind rather than introduce a ViewModelLocator:

public partial class FooView : UserControl
{
    public FooView()
    {
        InitializeComponent();
        this.DataContext = new FooViewModel();
    }
}

MainPage looks like this:

<StackPanel x:Name="LayoutRoot" Background="White">

    <Button Click="Button_Click" Content="Click for new FooView"/>
    <Button Click="Button2_Click" Content="Click to garbage collect"/>
    <ContentControl x:Name="myContent"></ContentControl>
</StackPanel>

with the following in the code behind:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        myContent.Content = new FooView();
    }

    private void Button2_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Memory in use after collection: " + (GC.GetTotalMemory(true) / 1024 / 1024).ToString() + "MB");
    }

Note: To replicate the problem, be sure to type something in the textbox, as I believe the Binding Expression isn't created until it's needed.

It's worth noting that this KB article may be related, however I'm not convinced since 'method 2' workaround doesn't seem to have an effect, and the reference chain doesn't seem to match.

Also, I'm not sure it matters, but I used CLR Profiler to diagnose the cause.

Update:

If anyone would like to test, and report their findings in a comment, I'm hosting the silverlight application via dropbox here: Hosted Example . To reproduce: Hit the top botton, type something, hit the top button, type something, hit the top button. Then hit the button. If it reports 10MB usage (or perhaps some other amount that is not increasing), you are not experiencing the memory leak.

Thus far, the problem seems to be happening on ALL of our development machines, which are ThinkPad w510 (43192RU) with 12GB Ram, 64 bit Win 7 Enterprise. This includes some which have not had development tools installed. It might be worth noting that they are running VMWare workstation.

The problem does NOT seem to happen on other machines I have tried - including several home PCs and other PCs in the office. We have somewhat ruled out SL versions, amount of memory, and probably vmware. Still haven't nailed down a cause.

Harrison answered 17/5, 2012 at 22:23 Comment(15)
Side note: One really ugly / crappy workaround for caliburn micro (and my current approach) is to set all private variables to null within the OnDeactivate(true) override. At least then we're leaking more slowly, but that's a bandaid at best.Harrison
If the leak is only there during runtime, you should take a look at this : #8544958Assign
I havent looked at that sample, however I would guess it's the same issue.Harrison
I ran example 1, and there was no leak. Memory stayed at 10MB no matter how many times I clicked "new FooView".Cornhusking
Did you type something into the textbox between each "new FooView" click?Harrison
I have tried your example 1 and it does not work as you expect.Zackzackariah
Bindins in silverlight is OneWay by default, so whatever you do, your FooViewModel.FooText will never be updatedZackzackariah
If I made some small changes, the memory used was 10742324, couple clicks later it changed to 10746260 (maybe some library loading ???) and than after almost 30 click and textchanges the value has not changed.Zackzackariah
Can I ask what version / platform of silverlight you're using? For me, if I [click button 1, type something, click button 2] I am reported 10 extra MB each time.Harrison
In fact, If i change the code to allocate 100mb of memory, it will throw an out of memory exception after 10 times (there seems to be a 1GB limit for SL)Harrison
My environment is W7, IE9, and SL5. I have tested it in VS2010 and VS11 beta targeting both SL4 and SL5. Memory grows up a little bit (300Bytes) at the beggining, but then it is constantZackzackariah
Looks like it might be an environment issue - I found a co-worker who doesnt experience the issue as well. I'm investigating the differences and will report back. Thanks!Harrison
I now have the silverlight app hosted on dropbox: dl.dropbox.com/u/72490893/MVVMLeak/MVVMLeakTestPage.html . I see the memleak behavior in my host OS, but not my VM. Not sure what to check at this point as I've verified they are the exact same versions of SL, running in the same browsers. Both are running the end-user runtime.Harrison
Have you considered re installing the framework? Have you checked for any updates that could have fixed your problem? I'd consider that route since it's an isolated event.Gayomart
All SL clients are up to date. All versions of 32bit / 64bit / developer / end-user run-times have been tried... Doesn't seem to fix the issue.Harrison
H
4

A solution is yet to be found, however the problem is now identified. This behavior will occur if Silverlights' automation faculties are invoked due to:

  • Tablet PC Input Service (in other words, all 'tablet like' PCs)
  • Automated Testing tools
  • Screen Readers (and other accessability software)

More information here: http://www.wintellect.com/cs/blogs/sloscialo/archive/2011/04/13/silverlight-memory-leaks-and-automationpeers.aspx

So a new problem surfaces: How do we disable automationpeers or otherwise get them to clean-up correctly?

This post illustrates one approach: WPF UserControl Memory leak

However, it isn't really a viable solution as we'd have to override every silverlight control which we plan to use binding for, not to mention the control templates of complex controls.

I will change my answer if anyone can identify a good solution, but for now there doesn't seem to be one...

Edit:

Here is a nice little workaround which seems to do the job. Simply add the following parameter in your HTML where you define the silverlight object:

<param name="windowless" value="true" />

A side-effect of running in 'windowless' mode is that automation doesn't work :)

Harrison answered 23/6, 2012 at 20:34 Comment(2)
I take it there is no such workaround for out-of-browser apps?Grudge
And by the way, I can't thank you enough for having researched this. Apparently no one else cares.Grudge
A
0

There is no memory leak in your second example.

After you affect a new FooView instance to your ContentControl using myContent.Content = new FooView();, there is no more used reference to the whole View + ViewModel object graph.

It'll be garbage-collected when needed, eventually.

Maybe you should clarify about what make you think there is a memory leak (i.e. statistics, repro steps...etc.)

Aerometer answered 27/5, 2012 at 12:20 Comment(1)
As mentioned in the update, the leak seems to be PC and/or environment specific to certain machines (possibly a small fraction of machines). I concur that there should not be a memory leak, but there definitely is one. Since the problem thus far only presents itself on our thinkpad w510 pcs, I have tabled the problem for now since I have run out of ideas.Harrison

© 2022 - 2024 — McMap. All rights reserved.