简体   繁体   English

绑定到列表导致内存泄漏

[英]Binding to list causes memory leak

When I bind an ItemsSource of a ListBox to a List the binding engine holds on to the list elements after the control is gone.当我将 ListBox 的 ItemsSource 绑定到 List 时,绑定引擎会在控件消失后保留列表元素。 This causes all the list elements to stay in memory.这会导致所有列表元素都留在内存中。 The problem goes away when using an ObservalbleCollection.使用 ObservalbleCollection 时问题就会消失。 Why does this happen?为什么会发生这种情况?

The xaml inside the window tag window标签内的xaml

<Grid>
    <StackPanel>
        <ContentControl Name="ContentControl">
            <ListBox ItemsSource="{Binding List, Mode=TwoWay}" DisplayMemberPath="Name"/>
        </ContentControl>
        <Button Click="Button_Click">GC</Button>
    </StackPanel>
</Grid>

Code behind:后面的代码:

public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }

private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.DataContext = null;
        ContentControl.Content = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

ViewModel视图模型

class ViewModel : INotifyPropertyChanged
{
    //Implementation of INotifyPropertyChanged ...

    //Introducing ObservableCollection as type resolves the problem
    private IEnumerable<Person> _list = 
            new List<Person> { new Person { Name = "one" }, new Person { Name = "two" } };

    public IEnumerable<Person> List
    {
        get { return _list; }
        set
        {
            _list = value;
            RaisePropertyChanged("List");
        }
    }

class Person
{
    public string Name { get; set; }
}

Edit: To check the leaking of the person istances, I used ANTS and .Net memory profiler.编辑:为了检查人员实例的泄漏,我使用了 ANTS 和 .Net 内存分析器。 Both show that after pushing the GC-button only the binding engine is holding reference to the person objects.两者都表明在按下 GC 按钮后,只有绑定引擎持有对 person 对象的引用。

Ahhh got you. 啊,你找到了 Now I understand what you mean. 现在我明白你的意思了。

You set the Content to null and so you kill the compelte ListBox but still the ItemsSource binds to List and so ListBox memory is not completely released. 您将内容设置为null,因此您杀死了强制ListBox但仍然将ItemsSource绑定到List,因此ListBox内存未完全释放。

That is unfortunately a well known issue and also well documented on MSDN. 遗憾的是,这是一个众所周知的问题,并且在MSDN上也有很好的记录。

If you are not binding to a DependencyProperty or a object that implements INotifyPropertyChanged or ObservableCollection then the binding can leak memory, and you will have to unbind when you are done. 如果您没有绑定到DependencyProperty或实现INotifyPropertyChanged或ObservableCollection的对象,则绑定可能会泄漏内存,并且您必须在完成后取消绑定。

This is because if the object is not a DependencyProperty or does not implement INotifyPropertyChanged or not implementing INotifyCollectionChanged (Normal list is not implementing this) then it uses the ValueChanged event via the PropertyDescriptors AddValueChanged method. 这是因为如果对象不是DependencyProperty或者没有实现INotifyPropertyChanged或者没有实现INotifyCollectionChanged(Normal list没有实现它),那么它通过PropertyDescriptors AddValueChanged方法使用ValueChanged事件。 This causes the CLR to create a strong reference from the PropertyDescriptor to the object and in most cases the CLR will keep a reference to the PropertyDescriptor in a global table. 这会导致CLR从PropertyDescriptor到对象创建强引用,并且在大多数情况下,CLR将在全局表中保留对PropertyDescriptor的引用。

Because the binding must continue to listen for changes. 因为绑定必须继续监听更改。 This behavior keeps the reference alive between the PropertyDescriptor and the object as the target remains in use. 当目标保持使用时,此行为使PropertyDescriptor和对象之间的引用保持活动状态。 This can cause a memory leak in the object and any object to which the object refers. 这可能导致对象中的内存泄漏以及对象引用的任何对象。

The question is...is Person implementing INotifyPropertyChanged? 问题是......是实施INotifyPropertyChanged的人吗?

I had a look at your example with JustTrace memory profiler and apart from an obvious question why would you kill view model / nullify DataContext and leave view running (in 99.9% of cases you'd kill View and DataContext - hence ViewModel and Bindings go of of scope automatically) here's what I found. 我看了一下你的JustTrace内存分析器的例子,除了一个明显的问题,你为什么要杀死视图模型/ nullify DataContext并让视图运行(99.9%的情况下你会杀死View和DataContext - 因此ViewModel和Bindings去了范围自动)这是我发现的。

It will work fine if you modify your example to: 如果您将示例修改为:它将正常工作:

  • replace DataContext with new instance of view model, as expected, existing instances of Person go out of scope as MS.Internal.Data.DataBingingEngine flushes all bindings, even they were strong refs not managed by WeakPropertyChangedEventManager , or: 将DataContext替换为新的视图模型实例,正如预期的那样,现有的Person实例超出了范围,因为MS.Internal.Data.DataBingingEngine刷新了所有绑定,即使它们是由WeakPropertyChangedEventManager管理的强引用,或者:
  • ViewModel to replace List with new instance of IEnumerable ie new Person[0]/simply null and raise INCP.PropertyChanged("List") on the ViewModel ViewModel用新的IEnumerable实例替换List,即新的Person [0] /只是null并在ViewModel上引发INCP.PropertyChanged(“List”)

Above modifications prove you can safely use IEnumerable/IEnumerable in binding. 以上修改证明您可以安全地在绑定中使用IEnumerable / IEnumerable。 BTW, Person class doesn't need to implement INPC neither - TypeDescriptor binding/Mode=OneTime don't make any difference in this case, I verified that too. BTW,Person类不需要实现INPC - TypeDescriptor绑定/ Mode = OneTime在这种情况下没有任何区别,我也验证了。 BTW, bindings to IEnumerable/IEnumerable/IList are wrapped into EnumerableCollectionView internal class. BTW,绑定到IEnumerable / IEnumerable / IList被包装到EnumerableCollectionView内部类中。 Unfortunatelly, I didn;t have a chance to go through MS.Internal/System.ComponentModel code to find out why ObservableCollection works when setting DataContext = null, probably because Microsoft guys did a special handing when unsubscribing from CollectionChanged. 不幸的是,我没有机会通过MS.Internal / System.ComponentModel代码来找出ObservableCollection在设置DataContext = null时的工作原理,可能是因为微软的人在取消订阅CollectionChanged时做了特殊处理。 Feel free to waste few precious lifetime hours on going through MS.Internal/ComponentModel :) Hope It helps 在通过MS.Internal / ComponentModel :)时,请尽量浪费宝贵的一生时间:)希望它有所帮助

That's an old post, I see.那是一个旧帖子,我明白了。 But the explanations provided especially by the accepted answer is not very accurate and its implications are wrong.但是,特别是接受的答案提供的解释不是很准确,其含义是错误的。

Abstract抽象的

Beforehand, this is not a real memory leak.事先,这不是真正的内存泄漏。 The special binding engine's lifetime management for collections that do not implement INotifyCollectionChanged and their associated CollectionView takes care of the allocated memory.未实现INotifyCollectionChanged集合的特殊绑定引擎的生命周期管理及其关联的CollectionView负责分配的内存。
WPF supports binding to many different types like DataTable and XML or in general to types that implement IList , IEnumerable or IListSource . WPF 支持绑定到许多不同的类型,如DataTable和 XML,或者通常绑定到实现IListIEnumerableIListSource If this was a serious bug, then all those bindings would be dangerous.如果这是一个严重的错误,那么所有这些绑定都是危险的。
Microsoft would propagate warnings in their docs against eg, binding to DataTable as they do in case of potential memory leaks in context with events or data binding. Microsoft 会在他们的文档中传播警告,例如绑定到DataTable就像在事件或数据绑定的上下文中发生潜在内存泄漏的情况一样。

It is indeed true that this special behavior can be avoided when binding to a collection of type INotifyCollectionChanged - or by avoiding using CollectionView at all!确实,当绑定到INotifyCollectionChanged类型的集合时,可以避免这种特殊行为 - 或者INotifyCollectionChanged避免使用CollectionView
This behavior is actually induced by the actual CollectionView management of the binding engine.这种行为实际上是由绑定引擎的实际CollectionView管理引起的。
The following code triggers the same behavior as would do binding to a List\\<T> :以下代码触发与绑定到List\\<T>相同的行为:

var list = new List<int> {1, 2, 3};
ICollectionView listView = CollectionViewSource.GetDefaultView(list);
list = null;
listView = null;
for (int i = 0; i < 4; i++)
{
  GC.Collect(2, GCCollectionMode.Forced, true);
  GC.WaitForPendingFinalizers();
}

Result: the entire collection reference tree and the CollectionView are still in memory (see explanation below).结果:整个集合引用树和CollectionView仍在内存中(见下面的解释)。
This should be proof that the behavior is not introduced by data binding, but the binding engine's CollectionView management.这应该证明该行为不是由数据绑定引入的,而是由绑定引擎的CollectionView管理引入的。


Memory Leaks in Context of Data binding数据绑定上下文中的内存泄漏

The memory leak issue regarding data binding is not related to the type of the property, but to the notification system that the binding source implements.数据绑定的内存泄漏问题与属性的类型无关,而与绑定源实现的通知系统有关。
The source must a) participate in the dependency property system (by extending DependencyObject and by implementing properties as DependencyProperty ) b) or implement INotifyPropertyChanged源必须 a) 参与依赖属性系统(通过扩展DependencyObject并通过将属性实现为DependencyProperty )b)或实现INotifyPropertyChanged

Otherwise the binding engine will create a static reference to the source.否则绑定引擎将创建对源的静态引用。 A static reference are root references.静态引用是根引用。 Due to their nature to be reachable during the lifetime of the application, such root references, like static fields and every object (memory) they reference, will never be elegible for garbage collection and thus create the memory leak.由于它们在应用程序的生命周期内可访问的性质,这样的根引用,如静态字段和它们引用的每个对象(内存),将永远无法进行垃圾回收,从而造成内存泄漏。

Collections and CollectionView Management集合和CollectionView管理

Collections are a different story.收藏是一个不同的故事。 The cause of the alledged leak is not the data binding itself.所谓的泄漏的原因不是数据绑定本身。 It's the binding engine that is also responsible for creating the CollectionView of the actual collections.绑定引擎还负责创建实际集合的CollectionView
Whether the the CollectionView is created in context of a binding or when calling CollectionViewSource.GetDefaultView : it's the binding engine that creates and manages the views.无论CollectionView是在绑定上下文中创建还是在调用CollectionViewSource.GetDefaultView时创建:它是创建和管理视图的绑定引擎。

Those views are managed by the ViewManager , which is part of the binding engine.这些视图由ViewManager管理,它是绑定引擎的一部分。 To improve performance, the view manager caches views: it stores them in a ViewTable using WeakReference to allow them to be garbage collected.为了提高性能,视图管理器缓存视图:它使用WeakReference将它们存储在ViewTable中以允许它们被垃圾收集。

The CollectionView itself is target of a strong reference from the underlying source collection if this collection implements INotifyCollectionChanged .如果CollectionView实现了INotifyCollectionChangedCollectionView本身就是来自底层源集合的强引用的目标。
This strong reference is created by the CollectionView the momement it observes the INotifyCollectionChanged.CollectionChanged event (by attaching a callback that the collection stores in order to invoke it when raising the event).这个强引用是由CollectionView在它观察到INotifyCollectionChanged.CollectionChanged事件时创建的(通过附加集合存储的回调,以便在引发事件时调用它)。

This way, the lifetime of the CollectionView is coupled to the lifetime of the collection: even if the application has no references to a CollectionView , because of this strong references the lifetime of the CollectionView is extended until the collection itself is elegible for garbage collection.这样, CollectionView的生命周期与集合的生命周期耦合:即使应用程序没有对CollectionView引用,由于这种强引用, CollectionView的生命周期会延长,直到集合本身可以进行垃圾收集。
Since the CollectionView instances are stored as WeakReference , this lifetime coupling prevents the WeakReference from getting garbage collected prematurely.由于CollectionView实例存储为WeakReference ,这种生命周期耦合可以防止WeakReference过早地收集垃圾。
This strong coupling prevents the CollectionView from being garbage collected before the collection.这种强耦合可以防止CollectionView在收集之前被垃圾收集。

Additionally, the manager must also guarantee that as long the CollectionView is referenced by the application, the underlying collection exists.此外,管理器还必须保证只要应用程序引用CollectionView ,底层集合就存在。 This is achieved by keeping a strong reference from CollectionView to the source collection.这是通过保持从CollectionView到源集合的强引用来实现的。 This is the crititical reference that results in the perceived memory leak.这是导致感知内存泄漏的关键参考。

Now, when the collection does not implement INotifyCollectionChanged , then the required strong reference from collection to CollectionView does not exist and the WeakReference to the CollectionView could be garbage collected prematurely.现在,当集合未实现INotifyCollectionChanged ,从集合到CollectionView所需的强引用不存在,对 CollectionView 的WeakReference可能会过早地被垃圾收集。 To fix this, the view manager must keep the CollectionView alive "artificially" to prevent the WeakReference .为了解决这个问题,视图管理器必须“人为地”保持CollectionView处于活动状态以防止WeakReference

It does this by storing a strong reference to the CollectionView .它通过存储对CollectionView的强引用来做到这一点。 At this moment the view manager has stored a strong reference to the CollectionView (due to the lack of INotifyCollectionChanged ) and this CollectionView has a strong reference to the underlying collection.此时,视图管理器已存储对CollectionView的强引用(由于缺少INotifyCollectionChanged )并且此 CollectionView 具有对基础集合的强引用。 This is the cause for the perceived memory leak.这是感知内存泄漏的原因。

But this is not a real leak as the view manager controls the lifetime of the strong reference to the CollectionView by registering the strong reference with an expiration date.但这并不是真正的泄漏,因为视图管理器通过注册具有到期日期的强引用来控制对CollectionView的强引用的生命周期。

The view manager will now occasionally purge those references when their expiration date is expired and the underlying collection was garbage collected.视图管理器现在偶尔会在过期日期到期并且底层集合被垃圾收集时清除这些引用。 This behavior is introduced to allow the strong reference while avoiding the leak.引入此行为是为了在避免泄漏的同时允许强引用。

Conclusion结论

Due to the special lifetime management (using expiration dates) for a CollectionView of a collection that does not implement INotifyCollectionChanged , the CollectionView is kept alive (in memory) much longer.由于未实现INotifyCollectionChanged的集合的 CollectionView 的特殊生命周期管理(使用到期日期), CollectionView保持活动状态(在内存中)的时间更长。 And because the CollectionView in general has a strong reference to its source collection (in this case the one without INotifyCollectionChanged ), this collection and its items and all rechable references are also kept alive much longer.并且因为CollectionView通常对其源集合有一个强引用(在这种情况下是没有INotifyCollectionChanged ),这个集合及其项目和所有可访问的引用也能保持更长时间。

If the collection had implemented INotifyCollectionChanged , then the view manager would not had stored the strong reference to the CollectionView and therefore the CollectionView would have been garbage collected the moment the source collection became unreachable.如果集合已实现INotifyCollectionChanged ,则视图管理器将不会存储对CollectionView的强引用,因此在源集合无法访问时, CollectionView将被垃圾收集。

The important point is, the lifetime of the strong reference to the CollectionView is managed by the ViewManager ie binding engine.重要的一点是,对CollectionView的强引用的生命周期由ViewManager即绑定引擎管理。 Due to the expiration date and the occasional purge, this lifetime is significantly extended.由于到期日期和偶尔的清除,此使用寿命显着延长。
Therefore, the observation of the persisting allocated memory after all references to the collection have been destroyed is perceiving.因此,在对集合的所有引用都被销毁后对持久分配的内存的观察是感知。 It is not a real memory leak.这不是真正的内存泄漏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM