Как можно освободить память, используемую тяжелыми элементами управления WPF детерминированным образом?

Я столкнулся с проблемами, когда элементы управления, выделяющие большие объемы памяти, уничтожаются и реконструируются с помощью событий input из пользовательского интерфейса.

Очевидно, что установка значения "Содержимое окна" равной null недостаточно для освобождения памяти, которую использовал содержащийся в ней элемент управления. В конце концов, он будет GCed. Но по мере того, как события input для уничтожения и создания элементов управления становятся все более частыми, GC, похоже, пропускает некоторые объекты.

Я разбил его на этот пример:

XAML:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" x:Name="window" MouseMove="window_MouseMove">
</Window>

C#:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private class HeavyObject : UserControl
        {
            private byte[] x = new byte[750000000];

            public HeavyObject()
            {
                for (int i = 0; i++ < 99999999; ) { } // just so we can follow visually in Process Explorer
                Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove
            }
        }

        public MainWindow()
        {
            InitializeComponent();

            //Works 1:
            //while (true)
            //{
            //    window.Content = null;
            //    window.Content = new HeavyObject();
            //}
        }

        private void window_MouseMove(object sender, MouseEventArgs e)
        {
            if (window.Content == null)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                // Works 2:
                //new HeavyObject();
                //window.Content = "Peekaboo!"; // change Content to el cheapo re-trigger MouseMove
                //return;
                window.Content = new HeavyObject();
            }
            else
            {
                window.Content = null;
            }
        }
    }
}

При этом выделяется объект ~750 МБ (HeavyObject) при каждом событии MouseMove и помещается в качестве содержимого главного окна. Если курсор остается в окне, изменение содержимого будет бесконечно повторно инициировать событие. Ссылка на HeavyObject обнуляется перед его следующей конструкцией, и запускается тщетная попытка GC его остатков.

Что мы можем видеть в ProcessExplorer / Taskman, так это то, что иногда выделяется 1,5 ГБ или более. Если нет, переместите мышь дико и уходите и повторно-войдите в окно. Если вы компилируете для x86 без LARGEADDRESSAWARE, вы получите исключение OutOfMemoryException (ограничение ~1,3 ГБ).

Вы не столкнетесь с таким поведением, если вы либо выделите HeavyObject в бесконечном цикле без использования ввода с помощью мыши (раскомментируем раздел «Работает 1»), либо если вы активируете выделение с помощью ввода мыши, но не поместите объект в визуальное дерево (раскомментируем seciton «Works 2»).

Поэтому я предполагаю, что это как-то связано с визуальным деревом, лениво освобождающим ресурсы. Но есть еще один странный эффект: если 1,5 ГБ потребляется во время того, как курсор находится за пределами окна, кажется, что GC не вступят в силу, пока MouseMove не будет запущен снова. По крайней мере, потребление памяти, по-видимому, стабилизируется до тех пор, пока не запускаются дальнейшие события. Таким образом, либо где-то осталась долгоживущая ссылка, либо ГК становится ленивым, когда нет активности.

Мне это кажется достаточно странным. Можете ли вы понять, что происходит?

Редактировать: Как прокомментировал БаламБалам:Данные, лежащие в основе Контроля, могут быть разыгранище перед уничтожением Контроля. Это был бы план Б. Тем не менее, возможно, есть более общее решение.

Говорить, что такие элементы управления являются плохим кодом, бесполезно. Я хотел бы знать, почему объект, на который я не держу ссылку, получает GCed, если я оставляю пользовательский интерфейс в покое на несколько тиков после его разыменования, но задерживается навсегда, если другой (который должен был быть создан пользовательским вводом) немедленно займет его место.

7
задан die_hoernse 24 January 2012 в 22:34
поделиться