ListCollectionView是否存在内存泄漏问题?

13

我一直在研究如何避免由于对视图模型中的INotifyCollectionChanged事件的强引用而导致的内存泄漏。我尝试使用ListCollectionView来解决这个问题。但是,我认为以下代码存在内存泄漏,我做错了什么吗?

var stuff = new ObservableCollection<string>();
while (true)
{
    var result = new ListCollectionView(stuff);
    // Just to keep make sure that the memory I'm seeing 
    // isn't waiting to be GC'd
    GC.Collect(); 
}

1
可能是 https://dev59.com/Iofca4cB1Zd3GeqPm71G 的重复问题,还要检查一下这个链接:http://www.eidias.com/blog/2014/2/24/wpf-collectionview-can-leak-memory。 - Simon Mourier
6个回答

15

关于ListCollectionView的文档不是很好,但是如果你注意到了,它有一个名为DetachFromSourceCollection的方法。这个方法的注释提到取消订阅并允许垃圾回收。

    var stuff = new ObservableCollection<string>();
    while (true)
    {
        ListCollectionView result = new ListCollectionView(stuff);

        //Use this method to unsubscribe to events on the underlying collection and allow the CollectionView to be garbage collected.
        result.DetachFromSourceCollection();
        //When finished set to null
        result = null;
        GC.Collect();
    }

2
@Geoff,这应该是你寻找的答案。DetachFromSourceCollection是你的ViewModel应该调用的东西。如果你不创建ListCollectionView,那么你就不必做任何事情(例如,当直接绑定到ObservableCollection时)。只有在你的集合没有实现INotifyPropertyChanged时才需要担心(因为它会泄漏)。 - Sinatr
只是确认一下,DetachFromSourceCollection的参考源显示了在_sourceCollection as INotifyCollectionChanged;中的事件处理程序被移除。 - dbc

10

我最初将这个问题作为评论发布,但我认为它更适合作为答案...

a) 如果你确定在.NET框架中发现了问题,那么你可能做错了什么。虽然不是不可能,但不太可能。 b) GC.Collect() 不会像你想象的那样起作用。

我认为你需要重新审视一下GC.Collect()的工作原理。


MSDN GC.Collect Method

备注

使用此方法尝试回收所有不可访问的内存。

所有对象,无论它们在内存中存在多长时间,都将被考虑进行收集; 但是,在托管代码中引用的对象不会被收集。使用此方法强制系统尝试回收可用内存的最大数量。


首先,你没有展示出你在哪里释放ListCollectionView(stuff)的内存。你只是分配新的内存,但你从未释放旧的内存。所以,它会像疯狂泄漏一样。直到垃圾回收运行并尝试收集。

如果你对一个字符串列表执行与此处所示相同的操作,它很可能会做同样的事情。但就你展示的内容而言,我预计它会泄漏。


谢谢你的回答!那么你会如何处理ListCollectionViews的释放? - Geoff
@Geoff:仅仅是不!垃圾被称为垃圾收集是有原因的。你为什么要处理它呢? - sehe
@Geoff,它正在“尝试”进行垃圾回收。但由于某些原因,它无法完成。垃圾回收有一些规则,例如对象的年龄。如果您刚刚创建了它,并且没有处理它,那么它不会轻易放弃它。它会在垃圾回收器中假定一个年龄。 - jcolebrand

2

CollectionView 持有对源集合的 CollectionChanged 事件的引用 - 因此只有在源集合被处理和收集后,GC 才能够对该视图进行垃圾回收。

这一点也可以从 CollectionView 的文档中清楚地看到。

    /// <summary>
    /// Detach from the source collection.  (I.e. stop listening to the collection's
    /// events, or anything else that makes the CollectionView ineligible for
    /// garbage collection.)
    /// </summary>
    public virtual void DetachFromSourceCollection()

此博客介绍了您的问题并提出了两种可能的解决方案:
http://www.eidias.com/blog/2014/2/24/wpf-collectionview-can-leak-memory ...


2
当您调用GC.Collect时,变量result仍在作用域内,因此由于数据存在一个指针,它不会被收集。无论如何,即使不是这种情况,垃圾回收对于应用程序代码而言也是不确定的。就像drachenstern所说,它只会尝试!并且它最终会成功,但您不能确定何时发生!

我想我应该解释一下,我并没有期望GC.Collect收集同一迭代中创建的变量,而是来自上一个迭代的变量。基本上,如果我不进行GC,我的笔记本电脑会在几分钟后冻结,而使用GC.Collect后,我会得到缓慢而稳定的增长。 - Geoff
是的,在每次迭代中,它应该收集上一次迭代的对象,但不是每次,因为垃圾回收工作在内存页面上,并不受分配顺序的影响。在垃圾回收中发生了什么是黑魔法!对于任何内存密集型操作,您应始终手动处理它。只有在处理泄漏的内存时才让垃圾回收处理它。 - Mehran
这可能很有趣:http://geekswithblogs.net/NewThingsILearned/archive/2008/02/07/disposing-collectionview-detaching-your-data-and-the-collectionview.aspx - Mehran

0

在每次迭代中,result 都会被重新分配,以便不会引用前一次迭代中的 ListCollectionView。但是,调用 GC.Collect 仅会安排这些项在 CLR 决定进行实际垃圾回收时回收其内存。如果您想更快地看到内存被回收,请尝试在调用 GC.Collect(); 后立即添加 GC.WaitForPendingFinalizers();


0

做这个的最好方法是使用作用域/匿名函数。Lambada非常适合这个。

var stuff = new ObservableCollection<string>();
ClosureDelegate closure = (x) => {
    ListCollectionView result = new ListCollectionView(x);
    //Use this method to unsubscribe to events on the underlying collection and allow the CollectionView to be garbage collected.
    result.DetachFromSourceCollection();
};

while (true)
{
    closure(stuff);
    GC.Collect(); 
}

使用这种方法,您将结果抛出作用域,因为其方法已被删除,并且这将尽可能少地使用内存。
摘自:https://msdn.microsoft.com/en-gb/library/bb882516.aspx?f=255&MSPPError=-2147217396

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