在 WPF PRISM/MVVM 应用程序中避免内存泄漏的最佳方法是什么
我有一个基于 PRISM 的 WPF 应用程序,它利用 MVVM 模式。
我注意到,有时我的视图模型、视图以及与它们相关的所有内容都会在其预期寿命结束后很长一段时间内一直存在。
一个泄漏涉及在属于注入服务的集合上订阅 CollectionChanged,另一个泄漏涉及不调用 DispatcherTimer 上的 Stop 方法,还有一个泄漏需要清除集合中的项目。
我觉得使用 CompositePresentationEvent 可能比订阅 CollectionChanged 更好,但在其他场景中,我倾向于实现 IDisposable 并让视图调用视图模型上的 Dispose 方法。
但是,随后需要告诉视图何时在视图模型上调用 Dispose,当视图的复杂性增加并且它们开始包含子视图时,这会变得更没有吸引力。
您认为处理视图模型以确保它们不会泄漏内存的最佳方法是什么?
预先感
谢伊恩
I have a WPF application based on PRISM that utilizes the MVVM pattern.
I have noticed that occasionally my view models, views and everything connected to them will hang around long after their intended lifespan.
One leak involved subscribing to CollectionChanged on a collection belonging to an injected service, another involved not calling the Stop method on a DispatcherTimer, and yet another required a collection be cleared of it's items.
I feel using a CompositePresentationEvent is probably preferable to subscribing to CollectionChanged, but in the other scenarios I am leaning towards implementing IDisposable and have the views call the Dispose method on the view models.
But then something needs to tell the view when to call Dispose on the view model, which gets even less attractive when the complexity of the views increase, and they start including child views.
What do you think is the best approach to handling view models, to ensure they don't leak memory?
Thanks in advance
Ian
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我可以告诉你,你经历过的痛苦我百分百经历过。我想我们是内存泄漏兄弟。
不幸的是,我在这里唯一想到要做的事情就是与您的想法非常相似的事情。
我们所做的是创建一个附加属性,视图可以将其应用于自身以将处理程序绑定到 ViewModel:
然后我们的 ViewModel 只是有一个类型为 Action 的方法:
当我的 close 方法触发时(我们有几种方法可以这样做...它是一个 TabControl UI,选项卡标题上带有“X”,类似的东西),我只需检查该视图是否已将其自身注册到 AttachedProperty。如果是这样,我调用那里引用的方法。
这是一种非常迂回的方法,只需检查视图的 DataContext 是否是 IDisposable,但当时感觉更好。检查 DataContext 的问题是您可能有也需要此控件的子视图模型。您要么必须确保您的视图模型链转发此处置调用,要么检查图中的所有视图并查看它们的数据上下文是否是 IDisposable(呃)。
我感觉这里好像缺少了一些东西。还有一些其他框架试图以其他方式缓解这种情况。您可以查看 Caliburn。它有一个处理此问题的系统,其中 ViewModel 知道所有子视图模型,这使其能够自动向前链接事物。特别是,有一个名为 ISupportCustomShutdown 的接口(我认为这就是它的名字),可以帮助缓解这个问题。
然而,我所做的最好的事情是确保并使用良好的内存泄漏工具,例如 Redgate Memory Profiler,它可以帮助您可视化对象图并找到根对象。如果您能够识别 DispatchTimer 问题,我想您已经在这样做了。
编辑:我忘记了一件重要的事情。 DelegateCommand 中的事件处理程序之一可能会导致内存泄漏。 Codeplex 上有一个关于它的帖子对此进行了解释。 http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4065
最新版本的 Prism (v2.1) 已修复此问题。 (http://www. microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en)。
I can tell you that I've experienced 100% of the pain you have experienced. We are memory leak brothers, I think.
Unfortunately the only thing I've figured out to do here is something very similar to what you are thinking.
What we've done is create an attached property that a view can apply to itself to bind a handler to the ViewModel:
Then our ViewModel just has a method on it of type Action:
When my close method fires (we have a few ways to do this... it's a TabControl UI with "X" on the tab headers, that kind of thing), I simply check to see if this view has registered itself with the AttachedProperty. If so, I call the method referenced there.
It's a pretty roundabout way of simply checking to see if the DataContext of a View is an IDisposable, but it felt better at the time. The problem with checking the DataContext is you might have sub view models that also need this control. You'd either have to make sure your viewmodels chain forward this dispose call or check all of the views in the graph and see if their datacontexts are IDisposable (ugh).
I sort of feel like there is something missing here. There are a few other frameworks that attempt to mitigate this scenario in other ways. You might take a look at Caliburn. It has a system for handling this where a ViewModel is aware of all sub view models and this enables it to automatically chain things forward. In particular, there is an interface called ISupportCustomShutdown (I think that's what it's called) that helps mitigate this problem.
The best thing I've done, however, is make sure and use good memory leak tools like Redgate Memory Profiler that help you visualize the object graph and find the root object. If you were able to identify that DispatchTimer issue, I imagine you are already doing this.
Edit: I forgot one important thing. There is a potential memory leak caused by one of the event handlers in DelegateCommand. Here's a thread about it on Codeplex that explains it. http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4065
The latest version of the Prism (v2.1) has this fixed. (http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en).
到目前为止我的发现...
除了 PRISM、Unity、WPF 和 MVVM 之外,我们还使用实体框架和 Xceed 数据网格。内存分析是使用 dotTrace 完成的。
我最终在我的视图模型的基类上实现了 IDisposable,并将 Dispose(bool) 方法声明为虚拟,允许子类也有机会进行清理。
当我们应用程序中的每个视图模型从 Unity 获取一个子容器时,我们也会对其进行处理,在我们的示例中,这可确保 EF 的 ObjectContext 超出范围。这是我们内存泄漏的主要来源。
视图模型被放置在基本控制器类上的显式 CloseView(UserControl) 方法中。它在视图的 DataContext 上查找 IDisposable 并对其调用 Dispose。
Xceed 数据网格似乎造成了相当一部分的泄漏,尤其是在长时间运行的视图中。任何通过分配新集合来刷新数据网格 ItemSource 的视图都应在分配新集合之前对现有集合调用 Clear()。
请小心实体框架并避免任何长时间运行的对象上下文。当涉及到大型集合时,这是非常无情的,即使您在打开跟踪的情况下删除了集合,它也会保留对集合中每个项目的引用,即使您不再保留它们。
如果您不需要更新实体,请使用 MergeOption.NoTracking 检索它,尤其是在绑定到集合的长期存在的视图中。
避免寿命较长的视图,当它们不可见时不要将它们保留在某个区域内,这会让您感到悲伤,特别是如果它们在可见时定期刷新数据。
在 Xceed 列上使用 CellContentTemplates 时,不要使用动态资源,因为资源将保存对单元格的引用,从而使整个视图保持活动状态。
在 Xceed 列上使用 CellEditor 且资源存储在外部资源字典中时,将 x:Shared="False" 添加到包含 CellEditor 的资源,资源将再次使用 x:Shared=" 保存对单元格的引用False”确保您每次都会获得一份新副本,并正确删除旧副本。
将 DelegateCommand 绑定到 Exceed 数据网格内的项目时要小心,如果存在绑定到命令的行上的删除按钮等情况,请务必在关闭视图之前清除包含 ItemsSource 的集合。如果您要刷新集合,您还需要重新初始化该命令,并且该命令将保存对每一行的引用。
My findings so far...
In addition to PRISM, Unity, WPF and MVVM we are also using Entity Framework and the Xceed data grid. Memory profiling was done using dotTrace.
I ended up implementing IDisposable on a base class for my view models with the Dispose(bool) method being declared virtual allowing sub classes the chance to clean up as well.
As each view model in our application gets a child container from Unity we dispose it as well, in our case this ensure that EF's ObjectContext went out of scope. This was our major source of memory leaks.
The view model is disposed within an explicit CloseView(UserControl) method on a base controller class. It looks for an IDisposable on the DataContext of the view and calls Dispose on it.
The Xceed data grid seems to be causing a fair share of the leaks, especially in long running views. Any view that refreshes the data grid's ItemSource by assiging a new collection should call Clear() on the existing collection before assigning the new one.
Be careful with Entity Framework and avoid any long running object contexts. It's very unforgiving when it comes to large collections, even though you have removed the collection if tracking is turned on, it will hold a reference to every item in the collection even though your no longer hanging on to them.
If you don't need to update the entity retrieve it with MergeOption.NoTracking, especially in long lived views that bind to collections.
Avoid views with a long life, don't hold onto them within a region when they are not visibile, this will cause you grief especially if they refresh their data at regular intervals when they are visible.
When using CellContentTemplates on the Xceed Column don't use dynamic resources as the resource will hold a reference to the cell, which in turn keep the entire view alive.
When using CellEditor on the Xceed Column and the resource is stored in an external resource dictionary add x:Shared="False" to the resource containing the CellEditor, once again the resource will hold a reference to the cell, using x:Shared="False" ensures you get a fresh copy each time, with the old one being removed correctly.
Be careful when binding the DelegateCommand to items within the Exceed data grid, if you have a case such as a delete button on the row which binds to a command, be sure to clear the collection containing the ItemsSource before closing the view. If you're refreshing the collection you also need to reinitialize the command as well as the command will hold a reference to each row.