Лучший способ избежать утечек памяти в приложении WPF PRISM/MVVM

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

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

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

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

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

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

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

Ян

Ответы

Ответ 1

Я могу сказать вам, что я испытал 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;
     }
}

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

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

Мне кажется, что здесь что-то не хватает. Есть несколько других структур, которые пытаются смягчить этот сценарий другими способами. Вы можете взглянуть на Caliburn. У этого есть система для обработки этого, где ViewModel осведомлен о всех моделях sub view, и это позволяет ему автоматически целенаправлять вещи вперед. В частности, есть интерфейс под названием 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

Мои выводы до сих пор...

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

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

Модель представления располагается в явном методе 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, перед закрытием представления. Если вы обновляете коллекцию, вам также нужно повторно инициализировать команду, а также команда будет содержать ссылку на каждую строку.