【问题标题】:Do subscriptions to rxjs Subjects cause memory leaks if not unsubscribed when the subject goes out of scope?如果主题超出范围时未取消订阅,订阅 rxjs 主题会导致内存泄漏吗?
【发布时间】:2020-08-05 02:55:43
【问题描述】:

在我的应用程序中,我有一些代表当地货币的对象,还有一些代表货币汇率的对象。

我的问题是,如果我的本地货币对象订阅了货币对象上的单个主题以提醒汇率变化(但货币对象实际上并没有保存订阅),然后单个货币实例定义了所有主题这些订阅设置为 null,如果我没有对 50,000 个货币对象中的每一个调用取消订阅,所有这些“订阅”会消失吗?

对于一个具体的(简化的)示例,如下:

import { Subject } from 'rxjs'
interface MyChangeEvent {
  oldValue : number;
  newValue : number;
}
export class Currency {
  rateSubject : Subject<MyChangeEvent>;
  private _rate : number;
  private _name : string;
  constructor(name : string, rate : number) {
    this.rateSubject = new Subject();
    this._rate= rate;
    this._name = name;
  }
  get rate() : number {
    return this._rate;
  }
  set rate(v : number) {
    let oldrate = this.rate;
    this._rate = v;
    let ce : MyChangeEvent
    ce = {} as MyChangeEvent;
    ce.newValue = v;
    ce.oldValue = oldrate;
    this.rateSubject.next(ce);
  }
}


export class Money {
  private _rate : number = 1;
  private _localCurr : number = 0;
 
  get dollarValue() {
    return this._localCurr * this._rate;
  }

  constructor(localCurr : number, curr : Currency) {
    this._localCurr = localCurr;
    this._rate = curr.rate;
    curr.rateSubject.subscribe((change)=>{
        this._rate = change.newValue;
    })
  }
}

const test = function() {
    let c = new Currency("USD", 1);
    let m = new Money(500, c);
    c.rate = .5;
    c=null;
}

所以我的问题是,假设我的应用程序中有 50,000 个货币对象,然后我在此处的最后一行设置 c=null。我为所有这些货币对象设置的 50,000 个侦听器是否会持续存在于内存中的某个位置,当货币对象超出范围时,它们是否都被垃圾回收了?

【问题讨论】:

  • 我猜 this.rateSubject = new Subject(); 的 JS 对象仍然保留,因为它包含对您传递给 subscribe 的回调的引用,这些回调在仍然在某处引用的所有货币对象的范围内跨度>
  • 我不确定我是否遵循。您是说货币对象保留对订阅的引用,即使货币对象本身不保存订阅?如果不是,我不理解为什么货币中的 rateSubject 不会是 GCd。只要没有活动对象引用它,它维护对回调的引用似乎还不够。基于对安德烈答案的投票达成的共识似乎不同意。但是,你有 77K 的代表,所以 - 你有多自信?

标签: typescript rxjs


【解决方案1】:

编辑

您也可以查看RxJS: Why memory leaks occur when using a Subject


我会说不会有内存泄漏。

这是基于我对实际发生内存泄漏的原因的理解。 通常这类问题发生在源是无限的时候(例如不会完成/出错,比如组件使用的全局服务)。

例如,在 Angular 中,一种常见的模式是将应用范围内的服务注入组件并订阅服务公开的可观察属性之一。

class Service {
  private usersSrc = new Subject();
  users$ = this.usersSrc.asObservable();
}

然后你会在你的组件中这样做:

class FooComponent {
  ngOnInit () {
    this.subscription = this.service.users$.subscribe(() => {} /* callback */)
  }
}

注意:这仅用于演示目的,因为您希望使用其他方法,这样您就不必手动订阅,例如异步管道

users$被订阅时,由于users$来自usersSrc,新创建的订阅者将被添加到Subject的订阅者列表中。该订阅者的下一个回调将是() =&gt; {} 回调。

现在,当组件被销毁时(例如由于导航到另一条路线),如果您不执行this.subscription.unsubscribe() 之类的操作,则该订阅者仍将是该订阅者列表的一部分。 unsubscribe 方法将从该列表中删除该订阅者。

所以,下次创建组件并创建ngOnInit 时,将添加一个新订阅者,但如果您不使用@987654332,旧订阅者仍然存在@。


我会说将源设置为 null 就足够了。

如果源恰好是Subject,您也可以使用Subject.unsubscribe,尽管它可能没有任何区别。

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

这将是一个简化版本。您可以将其粘贴到您的控制台中。

src = {
 subscribers: [],
 addSubscriber(cb) {
  this.subscribers.push(cb);
  return this.subscribers.length - 1
 },
 removeSubscriber(idx) {
  this.subscribers.splice(idx, 1)
 },
 next (data) {
  this.subscribers.forEach(cb => cb(data));
 }
}

// the component
class Foo {
 
constructor () {
   this.subIdx = src.addSubscriber(() => { console.log('foo') })
 }

 onDestroy () {
  src.removeSubscriber(this.subIdx);
 }
}

// usage

// creating a new component
foo = new Foo() // Foo {subIdx: 0}

// sending data to subscribers
src.next('test')

// destroying the component - without calling `onDestroy`
foo = null


src.next('test') // the subscribers is still there
VM506:18 foo

foo = new Foo() // registering a new instance - Foo {subIdx: 1}

src.next('test2')
foo
foo

foo.onDestroy()
src.next('test2')
foo

【讨论】:

  • 问题与Angular无关
  • 这是投反对票的理由?我只是举了一个例子,这应该能说明问题。你能详细说明这个答案有什么问题吗?最后一个例子也与角度无关,所以你可能想仔细看看。
  • 您的答案似乎与阿德里安的相冲突。 Adrian 建议订阅实例保持活动状态(为什么?),无论持有该主题的对象是否超出范围。这意味着在我的示例中,我有 50,000 个订阅指的是不存在的主题占用了我的应用程序中的内存?是真是假?
  • 我会说它们将被垃圾收集,因为它们不再被引用 - 持有它们的回调的主题,即被仔细考虑
  • 也就是说,我仍然会从我完成的任何订阅中退订。标准模式是使用一个名为 finalized 的无效主题,并将 takeUntil(finalised) 放在所有可观察对象上。然后,当您完成呼叫下一个并完成时完成。这在一次调用中完成了所有的 observables。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-24
  • 2020-09-20
  • 1970-01-01
  • 2015-08-11
  • 2021-12-30
  • 1970-01-01
相关资源
最近更新 更多