Распределение памяти в WPF происходит с использованием привязки, свойства inotifypropertychanged и зависимости - PullRequest
4 голосов
/ 29 июня 2011

Я пишу программу, которая использует кучу двусторонних привязок, и объем используемой памяти стал огромной проблемой. В моем полном приложении я начинаю с 50 МБ, а затем, просто используя привязки (т. Е. Изменяя значение на одной стороне и позволяя привязке обновлять другую сторону), я обычно ломаю 100 МБ, хотя мой код не выделил ничего нового , У меня вопрос, что это за лишняя память и что я могу сделать, чтобы ее контролировать. Я создал простой, воспроизводимый пример ниже:

Скажем, у меня есть главное окно со следующим содержимым:

<StackPanel Height="25" Orientation="Horizontal">
    <TextBox UndoLimit="1" Name="TestWidth" />
    <Label>,</Label>
    <TextBox UndoLimit="1" Name="TestHeight" />
</StackPanel>

И затем в конструкторе этого окна я создаю новое окно, показываю его, а затем привязываю его свойства зависимостей WidthProperty и HeightProperty к переменным, которые используют INotifyPropertyChanged:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private int _WidthInt;
    public int WidthInt
    {
        get { return _WidthInt; }
        set { _WidthInt = value; NotifyPropertyChanged("WidthInt"); }
    }

    private int _HeightInt;
    public int HeightInt
    {
        get { return _HeightInt; }
        set { _HeightInt = value; NotifyPropertyChanged("HeightInt"); }
    }

    public MainWindow()
    {
        InitializeComponent();
        Window testWindow = new Window();
        testWindow.Show();

        Binding bind = new Binding("HeightInt");
        bind.Source = this;

        bind.Mode = BindingMode.TwoWay;
        testWindow.SetBinding(Window.HeightProperty, bind);
        //bind.Converter = new convert();
        //this.TestHeight.SetBinding(TextBox.TextProperty, bind);

        bind = new Binding("WidthInt");
        bind.Source = this;

        bind.Mode = BindingMode.TwoWay;
        testWindow.SetBinding(Window.WidthProperty, bind);
        //bind.Converter = new convert();
        //this.TestWidth.SetBinding(TextBox.TextProperty, bind);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string sProp)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(sProp));
            GC.Collect();
        }
    }

Тогда, если я постоянно изменяю размер окна, использование памяти в диспетчере задач линейно увеличивается без видимого верхнего предела. Программа начинается с 17 МБ, и в течение 30 секунд после изменения размера она увеличивается до 20 МБ и зависает после определенной точки в низких 20-х (спасибо Иану). Это на самом деле происходит, даже если нет привязок, и память не возвращается обратно. Хотя это и раздражает, это не «скачок памяти», о котором я говорю.

Если я раскомментирую строки, которые также связывают текстовые поля с переменными, я получаю следующий результат: всего за несколько секунд он скачет с 18 МБ до 38 МБ и затем зависнет там (пожалуйста, обратите внимание на настройку привязки текстового поля в XAML не влияет на всплеск памяти). Я попытался реализовать свой собственный конвертер для привязки текстового поля, но это не влияет на использование памяти.

Переход по-прежнему существует, если я изменю переменные на новые свойства зависимостей и свяжусь с ними, например,

    public static readonly DependencyProperty WidthIntProperty = DependencyProperty.Register("WidthIntProperty", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0, null));
    int WidthInt
    {
        get { return (int)this.GetValue(WidthIntProperty); }
        set { this.SetValue(WidthIntProperty, value); }
    }
...
        Binding bind = new Binding("Text");
        bind.Source = TestHeight;
        bind.Mode = BindingMode.TwoWay;
        this.SetBinding(MainWindow.HeightIntProperty, bind);
        testWindow.SetBinding(Window.HeightProperty, bind);

или если я выполню привязку непосредственно между свойством text и свойством ширины и использую BindingMode.OneWay или наоборот.

Использование профилировщика CLR, кажется, не показывает мне, что выделяется, и у меня нет доступа к коммерческому профилировщику памяти. Может кто-нибудь объяснить мне, что хранится в памяти и как я могу от нее избавиться, сохраняя при этом функциональность непрерывного BindingMode? Должен ли я реализовать свой собственный метод привязки и обрабатывать события самостоятельно? Или есть что-то, что я могу регулярно смывать за пределами GC?

Спасибо за ваше время.

Ответы [ 2 ]

2 голосов
/ 01 июля 2011

Одна вещь, которую следует помнить с такими простыми программами, это то, что небольшое количество кода может в конечном итоге попасть в довольно большое количество инфраструктуры WPF.Например, с одним TextBox вы используете механизм компоновки, механизм свойств, систему шаблонов, систему стилей и специальные возможности.В тот момент, когда вы начинаете печатать, вы также вводите систему ввода, поддержку типографики и некоторую нетривиальную инфраструктуру интернационализации.

Эти два последних вполне могут быть большой частью того, что вы видите в этомконкретный пример.WPF автоматически использует множество возможностей шрифтов OpenType, что требует от него большой работы под прикрытием.(Как это бывает, шрифт пользовательского интерфейса по умолчанию на самом деле ничего не делает с ним, но вы все равно в конечном итоге платите цену за код, который обнаруживает, что Segoe UI не очень интересный шрифт.) Это сравнительно дорогая функция дляговоря, как тонкая разница это делает.Точно так же удивительно, как много входит в обработку ввода с учетом локали - сделать это полностью правильно с полной поддержкой i8n - это больше работы, чем думает большинство людей.

Вы, вероятно, в конечном итоге заплатите цену за каждую из этих подсистем,потому что TextBox не единственный кусок WPF, который их использует.Таким образом, усилия, необходимые для решений, созданных вручную, которые пытаются их избежать, в конечном итоге могут оказаться напрасными.

Крошечные тестовые приложения рисуют обманчивую картину - в реальном приложении заплаченная цена распределяется немного лучше.Ваша первая TextBox могла бы стоить ваших 30 МБ, но теперь вы загрузили множество вещей, которые все равно будет использовать остальная часть вашего приложения.Если бы вы начали с приложения, которое не использует ничего, кроме ListBox, вы могли бы затем добавить TextBox и сравнить разницу в потреблении памяти с базовым значением, равным ListBox.Это, вероятно, даст вам совершенно другую картину предельных затрат на добавление TextBox к вашему приложению.

Таким образом, вне контекста тривиального тестового приложения, усилия, необходимые для написания собственного текстового поля,может привести к очень небольшой разнице в частном рабочем наборе на практике.Вы почти наверняка закончите поиском по всем функциям и системам, которые я упомянул в первом параграфе, потому что TextBox - не единственное, что в WPF может их использовать.

Может ли каждая из этих систем бытьболее скромный?Без сомнения, они могли бы, но, к сожалению, WPF не имел такого большого технического вклада, как мне хотелось бы, что с отвлеченным вниманием к Silverlight, не говоря уже о слухах о том, что в Win8 будет еще одна попытка создания пользовательского интерфейса ...К сожалению, высокая степень использования памяти является особенностью WPF.(Хотя имейте в виду, что приложение WPF также имеет тенденцию использовать больше памяти на компьютере с большим объемом памяти. Требуется некоторое давление памяти, прежде чем его рабочий набор будет приведен к наиболее эффективному уровню.)

0 голосов
/ 01 июля 2011

Я действительно смущен этим ... Я думал, что все проверил и перепробовал столько подходов, сколько мог придумать, но я не думал всплеск памяти был из самого TextBox.Вот что вызывает всплеск.Если вы удалите все привязки и пух и просто укажете TextBox, даже с нулевым значением UndoLimit и ограничением MaxLength , объем памяти программы все равно будет увеличен на 15 Мб + после примерно дюжины правок содержимого TextBox,Таким образом, поскольку привязки также обновили текстовое поле, они вызвали этот всплеск.Я знаю, что элементы управления по умолчанию должны охватывать самые разные области применения, но, как моя первая программа на C # / WPF, я не осознавал, насколько они раздуты в этом случае.Я собираюсь написать свой собственный элемент управления TextBox и напомнить себе никогда не брать на себя слишком много, когда дело доходит до этого.Но, по крайней мере, теперь я могу поставить свой пользовательский код привязки в сторону!

...