我是否应该始终处理可观察对象的订阅?

Should I always dispose subscriptions of observables?

ViewModel 自动超出范围并且没有维护对其他 classes 的引用时,我是否应该始终处理 observable?

一个小例子:

public class TestViewModel : ReactiveObject
{
    public TestViewModel()
    {
        MyList = new ReactiveList<string>();
        MyList.ChangeTrackingEnabled = true;

        //Do I have to dispose/unsubscribe this?
        MyList.ItemChanged.Subscribe(_ => ...);

        //Or this?
        this.WhenAnyValue(x => x.MyList).Subscribe(_ => ...);
    }

    ReactiveList<string> _myList;
    public ReactiveList<string> MyList
    {
        get => _myList;
        set => this.RaiseAndSetIfChanged(ref _myList, value);
    }
}

根据我的理解,订阅是普通的 .NET 对象。在 ViewModel class 之外没有引用。因此,当我的 TestViewModel 超出范围时(即 object 不再使用并被另一个替换),GarbageCollector 应该清理 ViewModel 中的所有内容,所以我有 在返回的IDisposables.

上手动调用Dispose

我说得对吗?

编辑
ReactiveList 还可以包含其他 .NET 对象。此示例不特定于不可变字符串类型。

这是来自 Kent Boogart(ReactiveUI 维护者之一)对此事的看法:

所以,假设...如果您在视图中使用 WhenActivated,您什么时候处理它 returns 的一次性用品?您必须将其存储在本地字段中并使视图成为一次性视图。但是谁来处置这个观点呢?您需要平台挂钩来了解何时是处理它的适当时间——如果在虚拟化场景中重用该视图,这不是一件小事。就是这样。

当你执行反应命令时呢?你会储存你得到的一次性用品以便以后 "clean it up" 吗?我猜不会,而且有充分的理由。执行完成后,所有观察者都会自动取消订阅。通常,不需要手动处理对具有有限生命周期(例如,通过超时)的管道的订阅。处理这样的订阅与处理 MemoryStream.

一样有用

除此之外,我发现 VM 中的反应式代码尤其倾向于兼顾大量一次性用品。将所有这些一次性物品存放起来并尝试进行处置往往会使代码混乱并迫使 VM 本身成为一次性物品,这进一步混淆了事情。性能是另一个需要考虑的因素,特别是在 Android.

所以我的建议源于这一切。我发现通过将 需要 处理的那些订阅包裹在 WhenActivated 中来调出它们是最实用的方法。

要针对您的特定案例回答此类问题,您必须使用诊断工具来找出适合您案例的方法。

一项测试 运行 有 using 块,另一项没有:

class Program
{
    static void Main(string[] args)
    {
        //warmup types
        var vm = new TestViewModel();

        Console.ReadLine(); //Snapshot #1
        for (int i = 0; i < 1000; i++)
            Model();

        GC.Collect();
        Console.ReadLine();  //Snapshot #2
    }

    private static void Model()
    {
        using (var vm = new TestViewModel())
        {                
        }
    }
}

没有手动处理:

已处理两个订阅:

对于 1k 次迭代,差异很小,GC 正在执行其工作。 差异主要是 WeakReference 类型。

最终,正如 Glenn Watson 所说,您必须根据具体情况做出决定。使用周期性调度的 Observable 是手动处理的好选择。

ReactiveUI Guidelines for when to dispose subscriptions

你永远不应该依赖垃圾收集器来干净地处理你的对象。

Finalizer 有最长执行时间,可以按任何顺序执行,如果 Disposing 模式正确实施,Dispose 甚至不会被调用。

Dispose 应该清理托管资源,终结器应该清理非托管资源以避免内存泄漏。 Disposing(bool) 模式将在显式 Dispose 上调用,并且仅在垃圾收集时调用非托管模式。

显式 Dispose 还允许您更早地清理,而不是等到 GC 收集。

特别是对于订阅、事件处理程序等,不清除引用可能意味着当 GC 确实收集时,引用仍然存在,在这种情况下根本不会收集对象。

如果您知道没有到 root 的路径,您可能不必严格地显式处置,但在这种情况下,通常有人后来出现并在您知道之前不小心保留了对图中一个对象的引用,永远无法收集整个图表。