【问题标题】:Dispatcher's PriorityQueue causing "Memory Leaks"Dispatcher 的 PriorityQueue 导致“内存泄漏”
【发布时间】:2013-01-08 20:38:35
【问题描述】:

我在 WPF 中构建的 UI 框架中遇到垃圾收集问题。我在引号中使用内存泄漏,因为我认为我理解问题,但没有解决方案或某种解决方法。

当我创建 UIElements 但不显示它们时会出现此问题。从 UI 的角度来看,我使用的是虚拟化列表视图 (GridView)。所以所有的行都不会一次显示。在我的一个用例中,我创建了一个导出行功能,它能够将所有行导出到 csv。通常,行中的单元格由字符串或整数等基元组成。

棘手的地方在于,一些单元格本身就是 UIElement,这也是列表被虚拟化的原因之一(制作一些单元格的成本非常耗时,并且可能会占用内存)。所以我只在被问到时创建 UIElements,然后我什至不缓存它们(以使它们有资格被收集)。

当我在通过行提供程序枚举时访问属性时会出现此问题。创建并返回 UIElement,从中提取数据,写入 csv,然后查看下一行。在这个循环中,没有对这些行(ViewModel 类)的引用,因此人们会认为它们有资格进行垃圾收集。情况似乎并非如此。使用.Net Memory Profiler,我推断出我的“内存不足异常”是UIElements被PriorityQueue中的DispatcherOperation(来自Dispatcher)保留在内存中的结果。我认为这是因为它们正在等待显示(但从不显示)。

如果我没有遇到内存不足异常,最终这些 UIElement 似乎是通过 PriorityQueue 处理的(我猜它放弃了)并且被垃圾收集。这是最好的情况。当我处理少量行时,这似乎很好。但是当我们进入 50,000 - 100k 级别时,情况就不同了。我可以向您保证,没有其他对这些 ViewModel 或 UIElements 本身的引用(在 DispatcherOperation 之外)。

关于如何解决这个问题或解决这个问题有什么想法吗?有没有办法阻止这些未显示的、即将被使用的 UIElements 的排队?

2013 年 1 月 25 日编辑: 我意识到我可能需要专门清理内存中保存的内容。行对象,因为它们没有对 UIElement 的引用,它们没有保存在内存中(这很好)。只有通过 Get 访问器访问的 UIElement 会留在内存中。对它们的唯一引用是 PriorityQueue 而不是我自己的代码。

【问题讨论】:

  • 不能将元素绑定到(数据)数据源?
  • 我不确定你在问什么。最终由 ListView 转换为 ListViewItems 的 ViewModel 被创建并在它们进入视图时以编程方式绑定。没有问题,纯粹是在我创建 UIElements 但不显示它们时(我不想导出它们)。
  • UIElement 非常昂贵,创建十万个 UIElement 时无需泄漏即可获得 OOM。只是不要这样做,保持模型与视图分离。
  • 大部分情况是这样。实际上,我非常努力地保持这种情况,并且不希望我的导出功能了解有关“模型”的任何信息。在大多数情况下,我的视图模型确实将视图与模型分开。问题是我是否想在单个单元格中显示多个值。我见过做这样的事情的唯一方法是物理编写 xaml 并绑定到它(不可能)或使用单元格模板,我还没有开始正常工作。如果我只是将单元格的值设置为一个组合框,它就可以工作并且一切都很开心。
  • @flayn 你真的应该打开你自己的问题并提供一些实现细节。 WPF 控件在设计时考虑了 MVVM:它们通常不公开项容器 (UIElement),而是公开由容器包装的数据项。控件将视图(如何呈现数据或与之交互)与数据模型分开。 UIElement 旨在呈现数据。它不是为处理数据而设计的。为了访问数据而创建 UIElement 实例是没有意义的。如果是这种情况,那么您肯定做错了一切(由错误的心态导致)。

标签: c# wpf listview memory-leaks virtualization


【解决方案1】:

好吧,我不是垃圾回收专家,但我知道两件事:

  1. 您几乎不必关心垃圾回收
  2. 您几乎不必手动进行垃圾收集

这些是我对它了解不多的原因,我也不需要了解太多。

除此之外,这里是垃圾收集如何工作的粗略概述。

想象一下有这样的代码:

class Program
{
    class Something
    {
        public string name { get; set; }
    }


    class Container
    {
        List<Something> myList = new List<Something>();

        public void AddNewSomething()
        {
            Something mySomething = new Something() { name = "test" };
            myList.Add(mySomething);
        }
    }

    public static void Main(string[] args)
    {
        Container myContainer = new Container();
        myContainer.AddNewSomething();

        while (true)
        {
            Console.WriteLine("Something will always be in Memory");
        }
    }
}

垃圾收集是关于被引用的对象。所以看方法:AddNewSomething。方法完成后是否需要局部变量mySomething?该对象可以被垃圾收集吗?答案是否定的,因为对象mySomething 现在被myList 引用。

由于Main 中存在无限循环,myList 不能被垃圾回收,并且由于它包含MySomething 的对象,因此该对象也不能被垃圾回收,否则它将从列表中消失。

有意义吗?我希望如此。

鉴于 UIElements,我相信它们总是有父级吗?一个容纳它们的容器。该父级通常是一个具有子级列表的对象,就像在我的示例中一样。因此,除非父级不能被垃圾收集器处理,因为它有一个引用(例如它需要显示在窗口中),否则子级不能被垃圾收集器处理掉。因此,您添加的 UIElement 越多,孩子的列表就会变得越来越大。您必须主动将它们从父级中删除。缺少一些代码,所以我无法给出一个明确的例子,但你正在寻找类似的东西:

someParent.Children.Remove(noLongerRequiredUiElement);

但除此之外,您为什么要创建和添加您从未展示过的 UIElement?似乎您只是在处理一些数据,这些数据可以驻留在一个与 UI 完全无关的单独类中。鉴于您的问题,我并不完全清楚您为什么这样做。但我认为您可能会再次考虑您的类架构。这样您就可以在 Queue 中拥有项目,并在处理完后立即将它们一一删除。

【讨论】:

  • 问题是,UIElements 被创建但从未显示。这是由于某些我无法更改的代码(现在)。 “UIElements 被 PriorityQueue 中的 DispatcherOperation 保存在内存中(来自 Dispatcher)。”
猜你喜欢
  • 2023-03-14
  • 2015-07-06
  • 2014-06-07
  • 2013-11-20
  • 2011-10-28
  • 2016-01-18
  • 2012-12-13
  • 1970-01-01
  • 2011-01-08
相关资源
最近更新 更多