System.Timers.Timer утечка из-за "корней прямого делегата" - PullRequest
2 голосов
/ 21 апреля 2010

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

У нас есть WPF UserControl, который загружается сторонним приложением.Стороннее приложение - это презентационное приложение, которое загружает и выгружает элементы управления по расписанию, определенному XML-файлом, который загружается с сервера.

Наш элемент управления, когда он загружается в приложение, отправляет веб-запросвеб-сервис и использует данные из ответа для отображения некоторой информации.Мы используем архитектуру MVVM для управления.Точка входа элемента управления - это метод, который реализует интерфейс, предоставляемый основным приложением, и именно здесь настраивается конфигурация элемента управления.Здесь я также установил DataContext нашего элемента управления на MainViewModel.

. MainViewModel имеет две другие модели представления в качестве свойств, а основной UserControl имеет два дочерних элемента управления.В зависимости от данных, полученных от веб-службы, основной UserControl решает, какой дочерний элемент управления отображать, например, если есть ошибка HTTP или полученные данные недействительны, то отображать дочерний элемент управления A, в противном случае отображать дочерний элемент управления B. Каквы ожидаете, что эти два дочерних элемента управления связывают две отдельные модели представления, каждая из которых имеет свойство MainViewModel.

Теперь дочерний элемент управления B (который отображается, когда данные действительны) имеет свойство / поле RefreshService.RefreshService - это объект, который отвечает за обновление модели различными способами и содержит 4 System.Timers.Timer с;

  • a _modelRefreshTimer
  • a _viewRefreshTimer
  • a _pageSwitchTimer
  • a _retryFeedRetrievalOnErrorTimer (включается только когда что-топроисходит сбой при получении данных).

Здесь следует упомянуть, что существует два типа данных;первая меняется каждую минуту, вторая меняется каждые несколько часов.Конфигурация элементов управления определяет, какой тип мы используем / отображаем.

  1. Если данные относятся к первому типу, то мы обновляем модель довольно часто (каждые 30 секунд), используя события _modelRefreshTimer.

  2. Если данные относятся ко второму типу, то мы обновляем модель через более длительный интервал.Однако представление по-прежнему необходимо обновлять каждые 30 секунд, поскольку устаревшие данные необходимо удалять из представления (следовательно, _viewRefreshTimer).

Элемент управления также разбивает данные на страницы, поэтому мыможно увидеть больше, чем мы можем уместить на дисплее.Это работает, разбивая данные на списки и переключая свойство CurrentPage (которое является списком) модели представления в правый список.Это делается путем обработки события Elapsed _pageSwitchTimer.

Теперь проблема

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

Моим первым решением было внедрение IDisposable для RefreshService и выполните некоторую очистку, когда сработало событие UnLoaded элемента управления.В методе Dispose RefreshService s я установил Enabled на false для всех таймеров, затем использовал метод Stop() на всех них.Затем я тоже позвонил Dispose() и установил для них null.Ничего из этого не сработало.

После некоторого прочтения я обнаружил, что обработчики событий могут хранить ссылки на таймеры и предотвращать их удаление и сбор.После некоторого прочтения и исследования я обнаружил, что лучший способ обойти это - использовать Weak Event Pattern .Используя этот блог и этот блог, мне удалось обойти недостатки в шаблоне Weak Event.

Однако это не решает проблему. Таймеры все еще не отключены или не остановлены (не говоря уже о ликвидации), и веб-запросы продолжают накапливаться. Mem Profiler говорит мне, что «у этого типа есть N экземпляров, которые напрямую укоренены делегатом. Это может указывать на то, что делегат не был правильно удален» (где N - количество экземпляров). Насколько я могу судить, все слушатели события Elapsed для таймеров удаляются во время очистки, поэтому я не могу понять, почему таймеры продолжают работать.

Спасибо за чтение. С нетерпением жду ваших предложений / комментариев / решений (если вы получили это далеко :-p)

Ответы [ 3 ]

3 голосов
/ 21 апреля 2010

вы пытались использовать DispatchTimer вместо System.Timers.Timer?

Если в приложении WPF используется Timer, стоит отметить, что Timer работает в другом потоке, чем поток пользовательского интерфейса (UI). Чтобы получить доступ к объектам в потоке пользовательского интерфейса (UI), необходимо опубликовать операцию в диспетчере потока пользовательского интерфейса (UI) с помощью Invoke или BeginInvoke. Причины использования DispatcherTimer в отличие от Timer заключаются в том, что DispatcherTimer работает в том же потоке, что и Dispatcher, и можно установить DispatcherPriority. (из этого блога )

1 голос
/ 21 апреля 2010

Мне кажется, что сигналы таймера ставятся в очередь быстрее, чем они обрабатываются . Например, это может произойти, если каждое событие истечения занимает 2 секунды, а таймер истекает каждую секунду. Это приведет к тому, что в пуле потоков .NET запланировано отставание сигналов «поднять событие Elapse сейчас».

Такое отставание будет продолжать вызывать события истечения даже после того, как вы остановите таймер. Из документации System.Timers.Timer :

Даже если SynchronizingObject равен true, Прошедшие события могут произойти после Метод Dispose или Stop был вызван или после того, как свойство Enabled () имеет был установлен в ложь, потому что сигнал поднять прошедшее событие всегда поставлен в очередь на выполнение в пуле потоков нить. Один из способов решить эту гонку условие состоит в том, чтобы установить флаг, который говорит обработчик событий для прошедшего событие, чтобы игнорировать последующие события.

Чтобы избежать накопления необработанных сигналов таймера, накапливающихся в пуле потоков, вы можете установить для AutoReset значение false. Таким образом, таймер отключается при каждом истечении. После этого вы можете установить Enabled обратно в true после обработки события elapse. Таким образом, никакие дополнительные события не будут запланированы до тех пор, пока не будет обработано последнее событие.

0 голосов
/ 21 апреля 2010

Вызов timer.Elapsed - = new ElapsedEventHandler (); когда ваш элемент управления удален, чтобы вручную отсоединить обработчик.

...