Какой лучший способ избежать утечек памяти в приложении WPF PRISM / MVVM - PullRequest
13 голосов
/ 07 декабря 2009

У меня есть приложение WPF на основе PRISM, которое использует шаблон MVVM.

Я заметил, что иногда мои модели видов, виды и все, что с ними связано, будут зависать еще долго после предполагаемой продолжительности их жизни.

Одна утечка связана с подпиской на CollectionChanged для коллекции, принадлежащей внедренной службе, другая - без вызова метода Stop для DispatcherTimer, а еще одна - для очистки коллекции от ее элементов.

Мне кажется, что использование CompositePresentationEvent, вероятно, предпочтительнее подписки на CollectionChanged, но в других сценариях я склоняюсь к реализации IDisposable и заставляю представления вызывать метод Dispose в моделях представления.

Но тогда что-то должно сказать представлению, когда вызывать Dispose для модели представления, что становится еще менее привлекательным, когда сложность представлений возрастает, и они начинают включать дочерние представления.

Как вы думаете, что является лучшим подходом к работе с моделями представлений, чтобы они не пропускали память?

Заранее спасибо

Ian

Ответы [ 2 ]

14 голосов
/ 07 декабря 2009

Я могу вам сказать, что я испытал 100% боли, которую вы испытали. Я думаю, мы братья утечки памяти.

К сожалению, единственное, что я решил здесь сделать, это что-то очень похожее на то, что вы думаете.

Мы создали прикрепленное свойство, которое представление может применить к себе, чтобы привязать обработчик к ViewModel:

<UserControl ...
             common:LifecycleManagement.CloseHandler="{Binding CloseAction}">
...
</UserControl>

Тогда у нашей ViewModel есть метод типа Action:

public MyVM : ViewModel
{
     public Action CloseAction
     {
          get { return CloseActionInternal; }
     }

     private void CloseActionInternal()
     {
          //TODO: stop timers, cleanup, etc;
     }
}

Когда мой метод close срабатывает (у нас есть несколько способов сделать это ... это пользовательский интерфейс TabControl с "X" в заголовках вкладок, такого рода вещи), я просто проверяю, зарегистрировалось ли это представление само по себе с прикрепленным свойством. Если это так, я вызываю метод, на который есть ссылка.

Это довольно окольный способ просто проверить, является ли DataContext для представления идентифицируемым, но в то время он чувствовал себя лучше. Проблема с проверкой DataContext заключается в том, что у вас могут быть модели подвидов, которым также нужен этот элемент управления. Вам нужно либо убедиться, что ваши модели представления переадресовывают этот вызов dispose, либо проверить все представления на графике и посмотреть, являются ли их точки данных IDisposable (тьфу).

Я чувствую, что здесь чего-то не хватает. Есть несколько других структур, которые пытаются смягчить этот сценарий другими способами. Вы можете взглянуть на Caliburn . У него есть система для обработки этого, где ViewModel знает обо всех моделях подвидов, и это позволяет ему автоматически связывать вещи вперед. В частности, существует интерфейс под названием ISupportCustomShutdown (думаю, так он и называется), который помогает смягчить эту проблему.

Лучшее, что я сделал, однако, это удостоверился и использовал хорошие инструменты утечки памяти, такие как Redgate Memory Profiler, которые помогают вам визуализировать граф объектов и найти корневой объект. Если вам удалось идентифицировать эту проблему DispatchTimer, я думаю, вы уже делаете это.

Редактировать: Я забыл одну важную вещь. Существует потенциальная утечка памяти, вызванная одним из обработчиков событий в DelegateCommand. Вот нить об этом на Codeplex, которая объясняет это. http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4065

В последней версии Prism (v2.1) это исправлено. (http://www.microsoft.com/downloads/details.aspx?FamilyID=387c7a59-b217-4318-ad1b-cbc2ea453f40&displaylang=en).

2 голосов
/ 10 декабря 2009

Мои выводы пока ...

В дополнение к PRISM, Unity, WPF и MVVM мы также используем Entity Framework и сетку данных Xceed. Профилирование памяти было выполнено с использованием dotTrace.

В итоге я реализовал IDisposable в базовом классе для своих моделей представлений, при этом метод Dispose (bool) был объявлен виртуальным, что позволило подклассам также очиститься. Поскольку каждая модель представления в нашем приложении получает дочерний контейнер от Unity, мы также удаляем его, в нашем случае это гарантирует, что ObjectContext EF вышел из области видимости. Это был наш основной источник утечек памяти.

Модель представления располагается в явном методе CloseView (UserControl) базового класса контроллера. Он ищет IDisposable в DataContext представления и вызывает Dispose для него.

Сетка данных Xceed, по-видимому, вызывает значительную долю утечек, особенно при длительном просмотре. Любое представление, которое обновляет ItemSource сетки данных путем назначения новой коллекции, должно вызывать Clear () для существующей коллекции перед назначением новой.

Будьте осторожны с Entity Framework и избегайте любых длительных контекстов объектов. Это очень неумолимо, когда дело доходит до больших коллекций, даже если вы удалили коллекцию, если включено отслеживание, она будет содержать ссылку на каждый элемент коллекции, даже если вы их больше не держите.

Если вам не нужно обновлять объект, извлеките его с помощью MergeOption.NoTracking, особенно в долгоживущих представлениях, которые привязываются к коллекциям.

Избегайте представлений с длительным сроком службы, не держите их в пределах региона, когда они не видны, это вызовет у вас горе, особенно если они регулярно обновляют свои данные, когда они видны.

При использовании CellContentTemplates в столбце Xceed не используйте динамические ресурсы, поскольку ресурс будет содержать ссылку на ячейку, что, в свою очередь, поддерживает весь вид в действии.

При использовании CellEditor в столбце Xceed и сохранении ресурса в словаре внешних ресурсов добавьте x: Shared = "False" к ресурсу, содержащему CellEditor, еще раз ресурс будет содержать ссылку на ячейку, используя x: Shared = "False" гарантирует, что вы каждый раз получаете новую копию, причем старая удаляется правильно.

Будьте внимательны при привязке DelegateCommand к элементам в сетке данных Exceed. Если у вас есть случай, например кнопка удаления в строке, которая привязывается к команде, обязательно очистите коллекцию, содержащую ItemsSource, перед закрытием представления. Если вы обновляете коллекцию, вам также нужно повторно инициализировать команду, так как команда будет содержать ссылку на каждую строку.

...