Object Release Verification Framework
- modified:
- reading: 4 minutes
Такое случается, что при разработке приложения прокрадываются баги, а некоторые из них так же являются утечками памяти (Memory Leaks). Особенно такое явление бывает распространено, когда ваше приложение работает с документами или с сессиями, которые можно открывать и закрывать. Вам нужно убедиться, что при закрытии все, что было создано именно для этого документа, будет удалено из памяти, а так же все ресурсы будут освобождены. Особенно неприятно, когда вы нашли такой баг с утечкой памяти, исправили его, а после очередного обновления кода – он опять проявился.
Однажды, после одного из исправлений утечки памяти – у меня всплыла мысль, неужели нет каких-нибудь инструментов, которые просто могут валидировать, что после закрытия все мои объекты удалены, что после очередного моего изменения кода я не вызову новых утечек памяти? В native мире, на самом деле, таких инструментов предостаточно (но, к сожалению, я не могу вам их назвать, так как не являюсь экспертом в этой области), в managed тоже полно, но они требуют запускать инструменты параллельно. Такие инструменты обычно имеют слова Memory Profiler в названии, а запускать их нужно параллельно с вашим приложением, чтобы проверять на утечки памяти, да еще их результат нужно постоянно анализивароть. Мне же захотелось использовать что-то в коде, какой-то инструмент, который я бы мог отключать, и который будет присутствовать на всем этапе разработки, обладающий возможностью мгновенно отреагировать на утечку памяти в моем коде. Решение, на самом деле, оказалось совсем простым. Я попробовал, основываясь на Weak Reference (слабые связи) классах создавать коллекцию объектов, за которыми мне нужно следить, и которые должны быть удалены из памяти в определенный момент. Основываясь на этом принципе, я создал очень простой Object Release Verification Framework с несколькими классами и интерфейсами. Данный инструмент я опубликовал на nuget.org, как portable library.
Итак, давайте рассмотрим на примере. Создадим обычное WPF приложение (я буду использовать Visual Studio 2012), после этого добавим в проект nuget проект ObjectReleaseVerification при помощи команды в Package Manager Console (либо из GUI):
PM> Install-Package ObjectReleaseVerification
После этого нам нужно добавить инициализацию самого инструмента. Во-первых, по умолчанию, он отключен. А во-вторых, он не имеет никаких оповещателей о том, что существуют утечки памяти. Второе – это результат использования Portable Library, там уж очень не просто создать что-то, что будет работать и для Windows Phone, и для Web, и для Desktop приложений. Поэтому в коде нужно произвести инициализацию:
На старт приложения мы включаем инструмент только тогда, когда мы собираем приложение в Debug конфигурации, а так же добавляем один обработчик, который умеет записывать куда-нибудь информацию о том, какие объекты удалены, а какие нет. Класс TextOutputVefiricationHandler – часть инструмента, который ожидает в качестве параметра конструктора один метод, который может записывать куда-нибудь строки, в нашем случае – это output окно отладчика.
Дальше мы сымитируем работу с документами в самом окне приложения:
Обычное окно с парой кнопок, которое будет создавать и удалять табы (так скажем – они и будут нашими документами). Теперь добавим код:
В методе AddTab мы добавляем новый таб, используем DateTime.Now для имени нашего «документа», а так же, чтобы была возможность сымитировать утечку памяти, мы используем список для хранения табов. Самой последней строкой в методе AddTab мы добавляем новый объект TabItem в коллекцию объектов, за которыми мы будем следить, с определенным context. Context – это уникальное имя, которое может группировать объекты в разные списки, чтобы была возможность валидировать только определенный список объектов (объектов относящихся, например, к одному и тому же документу).
В методе RemoveTab мы удаляем таб из нашей коллекции, а так же из контрола, а после этого вызываем проверку того, что все объекты в контексте удалены. Нашим контекстом является имя «документа», которое мы создаем в AddTab. Делаем проверку мы с задержкой в 1 секунду, чтобы убедиться, что мы выйдем из метода. Метод Verify вызовет принудительно сборщик мусора, а затем проверит, какие объекты удалены, а какие нет.
Вот как это все теперь будет выглядеть:
На данном скриншоте я сначала добавил пару табов, потом удалил последний. В Output окне можно увидеть, что Object Release Verification Framework вывел мне информацию, что один из объектов удален.
Так же очень просто написать свой собственный VerificationHandler, который, например, будет показывать Assert, когда верификация будет неудачной:
Нужно только не забыть добавить его в инициализацию инструмента:
А дальше, если закомментировать строку в RemoveTab, которая удаляет таб из коллекции:
То при тестировании можно посмотреть, как ведет себя инструмент, когда утечка памяти есть:
Данный пример можно скачать с https://github.com/outcoldman/OutcoldSolutions-ObjectReleaseVerification-Sample. Буду рад любым предложениям и комментариям.