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