Правильная очистка пользовательских элементов управления WPF

Я относительно новичок в WPF, и некоторые вещи, связанные с ним, мне совершенно чужды. Во-первых, в отличие от Windows Forms, иерархия элементов управления WPF не поддерживает IDisposable. В Windows Forms, если пользовательский элемент управления использовал какие-либо управляемые ресурсы, было очень легко очистить ресурсы, переопределив метод Dispose, реализованный в каждом элементе управления.

В WPF все не так просто. Я искал это несколько часов и наткнулся на две основные темы:

Первая тема - это Microsoft, четко заявляющая, что WPF не реализует IDisposable, поскольку элементы управления WPF не имеют неуправляемых ресурсов. Хотя это может быть правдой, они, похоже, полностью упустили тот факт, что пользовательские расширения в их иерархии классов WPF действительно могут использовать управляемые ресурсы (прямо или косвенно через модель). Не реализовав IDisposable, Microsoft фактически удалила единственный гарантированный механизм, с помощью которого можно очистить неуправляемые ресурсы, используемые настраиваемым элементом управления WPF или окном.

Во-вторых, я нашел несколько ссылок на Dispatcher.ShutdownStarted. Я попытался использовать событие ShutdownStarted, но оно не срабатывает для каждого элемента управления. У меня есть куча WPF UserControl, я реализовал обработчик для ShutdownStarted, и он никогда не вызывается. Я не уверен, работает ли он только для Windows или, возможно, для класса приложения WPF. Однако он не срабатывает должным образом, и я сливаю открытые объекты PerformanceCounter каждый раз, когда приложение закрывается.

Есть ли лучшая альтернатива очистке неуправляемых ресурсов, чем событие Dispatcher.ShutdownStarted? Есть ли какой-нибудь трюк для реализации IDisposable, при котором будет вызываться Dispose? Я бы предпочел избегать использования финализатора, если это вообще возможно.


person jrista    schedule 11.10.2009    source источник


Ответы (5)


Боюсь, что Dispatcher.ShutdownStarted действительно кажется единственным механизмом, который WPF предоставляет для удаления ресурсов в UserControls. (См. Очень аналогичный вопрос, который я задал некоторое время назад).

Другой способ решения проблемы - переместить все ваши одноразовые ресурсы (если это вообще возможно) из кода позади и в отдельные классы (например, ViewModel при использовании шаблона MVVM). Затем на более высоком уровне вы можете обработать закрытие главного окна и уведомить все ViewModels через класс Messenger.

Я удивлен, что вы не получаете событие Dispatcher.ShutdownStarted. Прикреплены ли ваши UserControls к окну верхнего уровня в это время?

person Mark Heath    schedule 11.10.2009
comment
+1 за перемещение одноразовых ресурсов из кода программной части. Одним из ключевых моментов обучения WPF является минимизация отставания кода, чтобы воспользоваться преимуществами силы и выразительности архитектуры привязки данных. Это болезненная вещь для изучения (кривая обучения больше похожа на восхождение на скалу), но полезно, когда вы получаете способ мышления WPF. - person Greg D; 11.10.2009
comment
Все одноразовые ресурсы фактически находятся в ViewModel, которые сами по себе являются IDisposable. Я действительно не понимаю, почему событие Dispatcher.ShutdownStarted не запускается. Элемент управления счетчиком производительности (и связанная с ним ViewModel) действительно прикреплен к графику WPF, поскольку он встроен в ‹Grid› в ‹TabControl›. - person jrista; 11.10.2009
comment
@Greg D: Обычно я получаю модель WPF. Я начал использовать MVVM, как только понял основы WPF, и мой CodeBehind практически не имеет ничего общего (просто конструктор по умолчанию и его вызов InitializeComponent). Возможности компоновки и привязки данных в WPF невообразимы, и я никогда не вернусь к формам Windows, если у меня будет выбор. - person jrista; 11.10.2009

Интерфейс IDisposable (почти) не имеет смысла в WPF, потому что механизм отличается от Winforms. В WPF вы должны помнить о визуальном и логическом дереве: это фундаментально.
Итак, любой визуальный объект обычно является дочерним по отношению к другому объекту. Основа механизма построения WPF - иерархическое присоединение визуального объекта, затем отсоединение и уничтожение, когда они бесполезны.

Я думаю, вы можете проверить метод OnVisualParentChanged, представленный с момента UIElement: этот метод вызывается либо при присоединении визуального объекта, либо при отсоединении. Это может быть подходящее место для размещения неуправляемых объектов (сокетов, файлов и т. Д.).

person venezia    schedule 11.10.2009
comment
Спасибо за отзыв о OnVisualParentChanged. Я поиграю с этим и посмотрю, поможет ли это решить мою проблему. - person jrista; 11.10.2009

Я тоже это искал и после тестирования разных вариантов реализовал решение venezia

protected override void OnVisualParentChanged(DependencyObject oldParent)
    {
        if (oldParent != null)
        {
            MyOwnDisposeMethod(); //Release all resources here
        }

        base.OnVisualParentChanged(oldParent);
    }

Я понял, что когда родительский вызов Children.Clear() Method и уже добавил элементы в Children, DependencyObject имеет значение. Но когда родитель добавил элемент (Children.Add(CustomControl)), а дочерние элементы были пустыми, DependencyObject был равен нулю.

person Jaime Marín    schedule 28.12.2012
comment
Я изменил его на if (Parent == null), чтобы при перемещении элемента управления в другой контейнер он не самоуничтожался. - person Sean; 07.12.2017

В то время как другие предоставили вам действительно полезную информацию об этой проблеме, есть небольшая информация, которой вы, возможно, не располагаете, которая многое объясняет, почему нет IDisposable. По сути, WPF (и Silverlight) интенсивно использует WeakReferences - это позволяет вам ссылаться на объект, который GC все еще может собирать.

person Pete OHanlon    schedule 11.10.2009
comment
Спасибо за понимание, Пит. Мне любопытно, есть ли у вас ссылки, которые объясняют это более подробно? Мне любопытно, насколько интенсивное использование WeakReferences не вызывает проблем. Они могут быть мощным инструментом в нишевых ситуациях ... но я не могу представить, как они используются в WPF. - person jrista; 12.10.2009

У меня возникли трудности, когда я использовал какое-то соединение с базой данных, используя какой-нибудь драйвер реализации IDbConnection или Entity Framework.

Я обнаружил, что в этих случаях рекомендуется сохранять одно соединение / контекст объекта для каждого окна, чтобы иметь возможность отслеживать изменения / транзакции. (ссылка)

Итак, я переопределил OnClosing:

protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
      base.OnClosing(e);
      this._context.Dispose();
}

Контекст может быть моей ViewModel / Control, который реализует IDisposable для очистки ресурсов.

Пример использования OnClosing для удаления ресурсов (ссылка )

person fsbflavio    schedule 28.10.2020