ReactiveUI ObservableForProperty 生命周期

11

我对ObservableForProperty的生命周期很感兴趣,尤其是在观察者没有显式调用Dispose时。在这种情况下,我并不关心订阅时间过长等问题。

在传统的.NET中,如果你有事件而没有取消订阅,可能会导致内存泄漏,因为对象生命周期与事件绑定在一起。例如,在http://msdn.microsoft.com/en-us/magazine/cc163316.aspx建议的情况下:

事件也可以成为强根引用,并且可以贡献到强引用路径,从而影响对象的生命周期。普通事件在公共语言运行时(CLR)2.0中是事件源和侦听器之间双向的强引用,因此可以使一个对象(源或侦听器)保持活动状态,而该对象本来应该已经死亡。

在查看ReactiveUI代码库时,遇到INotifyPropertyChanged对象时,我注意到您正在使用FromEventPattern订阅INotifyPropertyChange事件。

使用ObservableForProperty能否解决通过创建强引用路径来保持对象活动状态的问题?

谢谢, 格伦


我在另一个帖子中注意到你提到ObservableForProperty在构造函数方面有一些上下文,只是好奇它是如何工作的。 - Glenn Watson
ObservableForProperty的机制取决于您使用它的对象,但是当Observable不再有订阅者时,其资源总是会被释放。 - Ana Betts
1个回答

20

你是正确的,如果使用WhenAny / ObservableForProperty不当,可能会导致应用程序泄漏内存,如果不小心的话。考虑以下代码:

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);
}

因为我们已经通过一个生命周期比我们更长的对象进行了WhenAny(即ListBox项目与Window),所以我们会一直持有ListBox项目,直到Window消失(在您的应用程序中可能永远不会消失)。

如果您只对自己的对象进行WhenAny(即始终使用this.WhenAny,而不是someObject.WhenAny),则可以避免绝大多数这些情况。

关于依赖属性的特别说明

无论如何,您必须Dispose任何通过DependencyProperty进行的WhenAny,否则就会泄漏。因为Windows。

该怎么办?

ReactiveUI添加了一个新功能来处理您确实希望这样做的情况,称为“Activation”。您可以在以下链接中找到更多信息:

我们现在可以定义一个范围,即“仅在屏幕上活动时应处于活动状态的事物”,该范围将在视图及其视图模型从屏幕中移除(即从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));
    });
}

为了让它工作,需要满足以下条件:

  1. 您的VieWModel需要实现ISupportsActivation(非常简单)
  2. 与您的ViewModel相关联的View也需要调用WhenActivated

这听起来超级难!

看起来是这样,但实际上并不是。只需记住两件事:

  1. 不要通过永久存在的对象进行WhenAny操作,除非您也永久存在
  2. 如果必须这样做,请使用WhenActivated。

很好,一定要试试看。不过为什么不在接口ISupportsActivation中添加一个方法“IEnumerable<Disposable> Activate()”,以便您可以从中产生所有订阅并在停用时进行处理。 - bradgonesurfing
总的来说,这是一个不错的想法,但当你涉及到继承时就会出现问题;如果你子类化并覆盖了方法,你可能会破坏基类(尽管我猜你可以争辩说你应该仍然调用基类的Activate方法)。 - Ana Betts
2
而C#没有像“yield foreach base.Activate();”这样简洁的语法,所以你必须手动编写“foreach(var v in base.Activate()){yield v;}”。 - bradgonesurfing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接