调度程序的优先队列引起“内存泄漏”问题

7
我在WPF中构建的UI框架中遇到了垃圾回收问题。我使用引号中的内存泄漏,因为我认为我理解了问题,但没有解决方案或某种解决方法。
此问题发生在创建UI元素但不显示它们时。从UI角度来看,我使用的是虚拟列表视图(GridView)。因此,并非所有行都会同时显示。在我的一个用例中,我创建了一个导出行特性,可以将所有行导出到csv。通常,行中的单元格由诸如字符串或整数之类的原始数据组成。
麻烦的是,某些单元格本身就是UI元素,这也是列表虚拟化的原因之一(制作某些UI元素的成本很高,可能会消耗大量内存)。因此,我只会在需要时创建UI元素,然后甚至不会对它们进行缓存(使它们有资格进行回收)。
当我在枚举行提供程序时访问属性时,问题就出现了。 UI元素被创建并返回,从中提取数据,写入到csv中,然后查看下一行。在此循环中,没有对这些行(ViewModel类)的引用,因此人们会认为它们应该有资格进行垃圾回收。但是事实似乎并非如此。使用.NET内存分析器,我推断出我的“内存不足异常”是由于DispatcherOperation(来自Dispatcher中的PriorityQueue)中保存了UI元素的原因。我假设这是因为它们正在等待显示(但从未实现)。
如果我没有得到内存不足异常,最终这些UI元素似乎会被处理掉(我猜测它放弃了),并且被垃圾收集。这是最好的情况。当我处理少量行时,这似乎没什么问题。但是当我们涉及50000-100k级别时,情况就另当别论了。我可以向您保证,这些ViewModel或UI元素本身(除了DispatcherOperation之外)都没有其他引用。
有什么想法可以解决或绕过这个问题吗?是否有办法阻止这些未显示且即将不再使用的UI元素排队?
编辑01/25/2013: 我意识到我可能需要澄清具体保存在内存中的内容。行对象没有对UI元素的引用,因此它们不保存在内存中(这很好)。唯一留下的是通过Get访问器访问的UI元素。它们唯一的引用是PriorityQueue,并没有我的代码。

2
UIElement非常昂贵,当您创建十万个时,不需要泄漏即可获得OOM。只需不要这样做,将模型与视图分开。 - Hans Passant
也许有其他我不知道的方法,可以在不创建自己的UI元素的情况下在单个单元格中公开多个值吗?在某些情况下,我正在创建包含网格(3列1行)的ComboBoxes。 - Mike
有没有完整源代码的最终解决方案? - Kiquenet
不,我最终采用了完全不同的方法。我彻底剥离了单元格/行中的所有UIElement类型。我无法超越这一点,所以我决定处理少量数据类型,包括一些“复杂”类型(列表将变成列表框)等。 - Mike
5
@flayn 你真的应该开一个新问题并提供一些具体实现细节。WPF控件是以MVVM为设计思想的,它们通常不会暴露项容器(UIElement),而是暴露包装在容器中的数据项。控件将视图(如何呈现或与之交互的数据)与数据模型分离。UIElement被设计用于呈现数据,而不是处理数据。创建UIElement实例以访问数据毫无意义。如果是这种情况,那么你肯定是在以错误的思维方式来做事情。 - BionicCode
显示剩余6条评论
1个回答

2

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

  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,我相信它们始终有一个父级?容器保存它们。该父项通常是具有子项列表的对象,就像我的示例中一样。因此,除非父项由于有引用而无法被垃圾回收(例如,需要在窗口中显示它),否则子项无法由垃圾回收器处理。因此,添加的UIElements越多,子项列表就会越来越大。您必须积极从父项中删除它们。缺少一些代码,因此我无法给出清晰的示例,但您正在寻找类似以下内容的内容:
someParent.Children.Remove(noLongerRequiredUiElement);

除此之外,为什么要创建并添加从未显示的UI元素呢?似乎你只是在处理一些数据,这些数据可以存在于一个与UI无关的单独类中。鉴于你的问题,我不太清楚为什么你会这样做。但我认为你可能需要重新考虑你的类架构。这样,你就可以在队列中拥有项目,并在处理完它们后逐个删除。


问题在于,UI元素被创建但从未显示。这是由于一些我现在无法更改的代码所致。“UI元素被Dispatcher中的PriorityQueue中的DispatcherOperation占用内存。” - flayn

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