Распределение памяти WPF с использованием привязки, свойство inotifypropertychanged и dependency

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

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

<stackpanel height="25" orientation="Horizontal">
 <textbox undolimit="1" name="TestWidth">
 <label>,</label>
 </textbox></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 ответа

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

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

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

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

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

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


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

licensed under cc by-sa 3.0 with attribution.