情况
我们正在运行一个大型的WPF应用程序,它不会在相当长的时间内释放内存。这不是真正的内存泄漏,因为内存最终会被释放。我知道通常情况下,这不会被认为是一个问题。不幸的是,与WPF命令基础设施结合使用时,它会成为性能问题。请参见下面的详细描述。
结果
我们有一些自动化测试,执行典型用例。有些情况下工作正常,并及时释放内存。其他情况下会占用内存,直到客户端最小化、打开新窗口或发生触发Gen2收集的其他条件。
• 使用ANTS,我们看到对象没有GC Root,但有许多引用其他需要完成的对象。
• WinDbg没有显示任何准备完成的对象。
• 运行几个GC.Collect()
,GC.WaitForPendingFinalizers()
完全释放内存。
• 我们知道哪个UI操作导致高内存条件,但我们无法识别任何可疑的代码。
问题
我们将非常感谢任何关于调试此类问题的建议。
WPF CommandManager的背景
WPF CommandManager持有WeakReferences (_requerySuggestedHandlers
)的私有集合,用于引发CanExecuteChanged
事件。处理CanExecuteChanged
是相当昂贵的(特别是查找CanExecute
的EventRoute,它明显是一个RoutedEvent
)。每当CommandManager感觉需要重新查询命令是否可执行时,它都会遍历此集合,并在相应的命令源上调用CanExecuteChanged
事件。
只要引用的对象有GC句柄,就不会从该集合中移除WeakReferences。只要对象没有被收集,CommandHelper将继续处理这些元素(ButtonBase或MenuItems)的CanExecute事件。如果垃圾很多(如我们的情况),这可能导致CanExecute事件处理程序调用次数极其庞大,从而导致应用程序非常卡顿。