Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactiveUI ObservableForProperty lifetime

I am curious about the life time of ObservableForProperty lifetime when not explicitly calling the Dispose on the Observer. I don't really care in this scenario about getting subscriptions for too long etc.

In traditional .NET if you have events unless you unsubscribed it could potentially lead to memory leaks due to the fact your object lifetime is bound to the event. eg as suggested in http://msdn.microsoft.com/en-us/magazine/cc163316.aspx :

Events can also be strong root references and as such can contribute to the strong reference path and thus affect the lifetime of an object. Ordinary events in the common language runtime (CLR) 2.0 are bidirectional strong references between the event source and the listener and as such can keep an object (either source or listener) alive that otherwise should be dead already.

Looking through the ReactiveUI code base when encountering a INotifyPropertyChanged object, I notice you are using FromEventPattern subscribing to INotifyPropertyChange event.

Does using ObservableForProperty get around this problem of keeping object's alive for longer by creating the strong reference path?

Thanks, Glenn

like image 753
Glenn Watson Avatar asked Mar 12 '14 02:03

Glenn Watson


1 Answers

You are correct, using WhenAny / ObservableForProperty incorrectly can cause your application to leak memory if you are not careful. Consider the following code:

public ItemInAListBoxViewModel(MainWindowViewModel mainWindow)
{
    this.window = mainWindow;

    // Reset the "selected" when the user minimizes
    this.WhenAnyValue(x => x.window.IsMinimized)
        .Where(x => x == true)
        .Subscribe(x => this.IsSelected = false);
}

Because we've WhenAny'd through an object whose lifetime is longer than ours (i.e. the ListBox item vs the Window), we're holding ListBox items forever until the Window goes away (which may be never in your app).

You will avoid the vast majority of these cases if you only WhenAny on your own object (i.e. always this.WhenAny, never someObject.WhenAny).

A special note about Dependency Properties

No matter what, you have to Dispose of any WhenAny that goes through a DependencyProperty, or else you leak. Because Windows.

Crap, what do I do now?

A new feature was added to ReactiveUI to handle the scenario where you do want to do this though, called "Activation". You can find more info at:

  • The release notes for RxUI 5.5
  • The PR that added this feature.

We can now define a Scope of "things that should only be active when On Screen", that will go away immediately after a View and its ViewModel are removed from the screen (i.e. removed from the Visual Tree in WPF).

public ItemInAListBoxViewModel(MainWindowViewModel mainWindow)
{
    this.window = mainWindow;

    Activator = new ViewModelActivator();

    // This gets called every time the View for this VM gets put on screen
    this.WhenActivated(d => {
        // The 'd' is for "Dispose this when you're Deactivated"
        d(this.WhenAnyValue(x => x.window.IsMinimized)
            .Where(x => x == true)
            .Subscribe(x => this.IsSelected = false));
    });
}

For this to work, here's what needs to be true:

  1. Your VieWModel needs to implement ISupportsActivation (super easy)
  2. The View associated with your ViewModel needs to call WhenActivated too.

This sounds super hard!

It looks that way, but it's totally not. Just remember two things:

  1. Don't WhenAny through objects that stick around forever, unless you also stick around forever
  2. If you do have to, use WhenActivated.
like image 73
Ana Betts Avatar answered Sep 29 '22 23:09

Ana Betts