【问题标题】:Do I need to unsubscribe from an Observable if the Observable is finished with?如果 Observable 完成了,我是否需要取消订阅 Observable?
【发布时间】:2020-04-26 10:50:57
【问题描述】:

假设我有一个Observable(热门,未完成),我订阅了它。通常当我完成Subscription 后,我必须取消订阅它以防止内存泄漏。

let subject$ = new Subject();

const sub = subject$.subscribe(...);
...
// Need to call sub.unsubscribe() when we are finished
sub.unsubscribe();
sub = null;

但是,如果我不仅完成了 Subscription,还完成了 Observable (Subject) 并且删除了对两者的所有引用,我是否需要调用 unsubscribe 方法?

let subject$ = new Subject();

const sub = subject$.subscribe(...);
...
sub = null;
subject$=null;
// Assume I have no other references to these
// Do I need to call sub.unsubscribe() in this case?

我的逻辑告诉我我不这样做,因为 SubjectSubscription 现在都符合垃圾收集条件,并且将被销毁,即使它们相互引用。还是有一些我不知道的隐藏参考?

不用担心使用unsubscribetakeUntil或其他机制的区别。

【问题讨论】:

  • 您好,您提出了一个有趣的问题。根据我的经验,总是建议取消订阅,但由于您设置了subject$=null,这将确保不会发出其他排放,从而阻止再次调用订阅的回调函数的内存泄漏。顺便说一句,要完成一个主题,它有一个完整的方法(subject$.complete())。对您而言,由于 subject$ 对您来说是本地的,然后您将其设置为 null,因此这不是问题。但是如果 observable 来自外部源,比如 redux 存储,那么它可能是一个问题,我会取消订阅。
  • 另一件事是,如何退订。取消订阅的方法有很多,但我发现使用 async 管道(如果使用 Angular)和 takeUntil 运算符是最干净的。
  • 如果你能给出答案,最好有一些参考资料来支持它,那就太好了。 (不用担心“如何退订”部分)。
  • 如果对主题和订阅“没有其他引用”,那么不,您不必取消订阅即可对其进行垃圾收集。仅当在 RxJS 中保存了对其中任何一个的引用时才需要取消订阅,但情况并非如此 - 除非您使用诸如 rxjs-spy 之类的调试工具。

标签: javascript memory-leaks rxjs


【解决方案1】:

如果let subject$ = new Subject(); 清除对SubjectSubscription 的引用就足够了,之后所有内容都将被垃圾回收。

当您订阅对象中的Subject,并且在清除对象上的所有引用之前未取消订阅Subject 时,内存泄漏的风险就会变得真实。在这种情况下,整个对象将保持活动状态并且不会被垃圾回收。

我们来看这个例子:

class BigClass {
    constructor(observable) {
        this.bigArray = new Array(9999999).fill(0);
        observable.subscribe(x => this.result = x);
    }
    //...
}

let subject = new rxjs.Subject();
let bigObject = new BigClass(subject);
let bigObject1 = new BigClass(subject);
let bigObject2 = new BigClass(subject);
let bigObject3 = new BigClass(subject);

bigObject = null;
bigObject1 = null;
bigObject2 = null;
bigObject3 = null;

在本例中,当清除 bigObject 上的所有引用时,subject 仍然有对 x => this.result = x 回调的引用,而该回调对 bigObject 有引用,使其整体无法收集。

通过取消订阅或清除subject,这将破坏使bigObject 保持活动状态的引用链,它将有资格进行垃圾回收。

要自己观察行为,您可以在控制台中复制此文件https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js 的内容,然后复制粘贴示例代码。您会注意到任务管理器中的内存增加。在开发人员工具的内存选项卡中创建堆转储时,您可以通过在搜索字段中输入 BigClass 来找到这 4 个对象。

之后,在控制台中输入subject = null;,然后创建一个新的堆转储。您会注意到 4 个对象已消失。

总而言之,只要 Observable 被销毁,就没有真正的内存泄漏风险,因为所有订阅也会被销毁。有风险的 Observables 是永久的(例如:使用 fromEvent 附加到全局 DOM 事件),并且带有引用需要销毁的对象的回调。

【讨论】:

  • 不是我不相信你,而是你有参考吗?我还可以通过设置 subject=null 来检查在您的示例中是否可以释放内存?
  • 我已经添加了有关如何在您身边测试和观察这一点的详细信息
  • 哼,如果你也做 subject = null 呢?
  • @Ced 抱歉,我不明白你的意思
【解决方案2】:

不,你不需要

对于内存使用没有区别。

当您调用sub.unsubscribe(); 时,RXJS 唯一要做的就是将观察者设置为nullhere you can see the original RXJS unsubscribe code

  unsubscribe() {
    this.isStopped = true;
    this.closed = true;
    this.observers = null!;
  }

这些标志仅用于进一步验证。

不过我建议你坚持使用unsubscribe 方式,那是因为你永远不知道RXJS 会在未来的版本中添加什么。例如,他们可能会添加一个新功能,如下所示:

  unsubscribe() {
    this.isStopped = true;
    this.closed = true;
    this.observers = null!;
    if (this.coolNewFeature) {
        this.coolNewFeature.unsubscribe()
    }
  }

在那种情况下,您只执行subject = null; 的方法会导致内存泄漏。

【讨论】:

  • 为了记录,Observables 是 RXJS。
  • @DJClayworth,真相...我删除了 Angular 参考并添加了 RXJS,希望链接参考对您有所帮助;)
猜你喜欢
  • 2017-06-09
  • 1970-01-01
  • 2019-03-06
  • 2017-06-22
  • 2020-05-30
  • 2022-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多