DispatcherTimer со временем потребляет ресурсы ЦП, из-за чего визуальный элемент WPF не отображается должным образом

У меня есть приложение WPF, которое использует DispatcherTimer для обновления тика часов.

Однако после того, как мое приложение проработало около 6 часов, углы стрелок часов больше не меняются. Я убедился, что DispatcherTimer все еще работает с отладкой и что значения углов все еще обновляются, однако рендеринг экрана не отражает изменения.

Я также проверил с помощью инструментов WPFPerf Visual Profiler, что Unlabeled Time, Tick (Time Manager) и AnimatedRenderMessageHandler (Media Content) постепенно растут, пока не потребляют почти 80% ЦП, однако память работает стабильно.

hHandRT.Angle является ссылкой на RotateTransform.

hHandRT = new RotateTransform(_hAngle);

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

.NET 3.5, Windows Vista с пакетом обновления 1 (SP1) или Windows XP с пакетом обновления 3 (оба показывают одинаковое поведение)

EDIT: добавление функции тиканья часов

//In Constructor
...
_dt = new DispatcherTimer();
_dt.Interval = new TimeSpan(0, 0, 1);
_dt.Tick += new EventHandler(Clock_Tick);
...

 private void Clock_Tick(object sender, EventArgs e)
        {

            DateTime startTime = DateTime.UtcNow;
            TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById(_timeZoneId);
            _now = TimeZoneInfo.ConvertTime(startTime, TimeZoneInfo.Utc, tst);
            int hoursInMinutes = _now.Hour * 60 + _now.Minute;
            int minutesInSeconds = _now.Minute * 60 + _now.Second;
            _hAngle = (double)hoursInMinutes * 360 / 720;
            _mAngle = (double)minutesInSeconds * 360 / 3600;
            _sAngle = (double)_now.Second * 360 / 60;
            // Use _sAngle to showcase more movement during Testing.
            //hHandRT.Angle = _sAngle;
            hHandRT.Angle = _hAngle;
            mHandRT.Angle = _mAngle;
            sHandRT.Angle = _sAngle;

            //DSEffect
            // Add Shadows to Hands creating a UNIFORM light
            //hands.Effect = textDropShadow;
        }

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

  //DateTime startTime = DateTime.UtcNow;
  //TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById(_timeZoneId);
  //_now = TimeZoneInfo.ConvertTime(startTime, TimeZoneInfo.Utc, tst);
  _now = _now.AddSeconds(1);

person discorax    schedule 10.10.2009    source источник
comment
Я не знаю ответа, но это любопытная проблема. Я с нетерпением жду некоторых ответов.   -  person Greg D    schedule 12.10.2009
comment
Как часто вы тикаете на диспетчерском таймере?   -  person Greg D    schedule 12.10.2009
comment
@ Грег Я тоже добавил эту часть сейчас. Он обновляется каждую секунду.   -  person discorax    schedule 12.10.2009
comment
Можете ли вы также опубликовать свой xaml, где вы связываете эти элементы? Я считаю, что в долгосрочной перспективе визуальный рендерер прекратит рендеринг только в том случае, если некоторые настройки питания машины отключат монитор и некоторые процессы рендеринга, когда окна оживут, вы можете не увидеть процесс рендеринга таким же, как раньше. Вы уверены, что настройки питания правильные?   -  person Akash Kava    schedule 13.10.2009
comment
Нет XAML, о котором можно было бы говорить. Я создаю все элементы кода и добавляю их в корневой холст во время выполнения. Класс Clock расширяет класс Canvas.   -  person discorax    schedule 13.10.2009


Ответы (3)


Вы говорите, что каждый раз создаете экземпляр класса Clock? Обратите внимание, что таймеры в .NET будут рутировать себя, чтобы предотвратить сборку мусора. Они будут продолжать работать до тех пор, пока вы сами их не остановите, и они сохранят ваши объекты Clock живыми, потому что на них ссылаются в событии таймера.

Я думаю, что происходит то, что с каждыми часами, которые вы создаете, вы запускаете новый таймер. Сначала вы запускаете только 1 событие в секунду, но затем вы добавляете еще один таймер и получаете 2 события в секунду, и таким образом они продолжают накапливаться. В конце концов вы видите, что ваш обработчик Tick и AnimatedRenderMessageHandler увеличивают нагрузку на ЦП, пока они не зависнут и не смогут обновить ваш экран. Это также объясняет, почему увеличение частоты срабатывания таймера приводит к тому, что ваши симптомы появляются раньше.

Исправление должно быть простым: просто остановите или удалите DispatcherTimer, когда закончите с объектом Clock.

person RandomEngy    schedule 13.10.2009
comment
Я перестроил класс Clock, включив в него метод DisposeTimer, который вызываю непосредственно перед удалением экземпляра Clock из визуального дерева. Я запускаю тест прямо сейчас, чтобы увидеть, решит ли это проблему. Держу пальцы скрещенными. - person discorax; 13.10.2009
comment
переработка класса Clock, кажется, исправила некоторые проблемы. Поскольку я назначил вознаграждение за этот вопрос, мне пришлось принять ответ, однако проблема с DispatcherTimer все еще не решена. Я надеялся, что кто-то с опытом работы с несколькими потоками вмешается, но не тут-то было. Спасибо за вашу помощь. - person discorax; 18.10.2009
comment
Что заставляет вас полагать, что проблема связана с самим DispatcherTimer, а не с их количеством? Вы проверили, как часто вызывается ваш метод Clock_Tick? DispatcherTimer довольно надежен: через равные промежутки времени он просто помещает метод в очередь для вызова потока пользовательского интерфейса. Если ваше приложение зависает из-за чрезмерного использования ЦП потоками пользовательского интерфейса и рендеринга, это больше не проблема многопоточности, а скорее вопрос о том, почему я вызываю так много вызовов потока пользовательского интерфейса? - person RandomEngy; 18.10.2009
comment
Вы упомянули AnimatedRenderMessageHandler в своем анализе. Я видел спорадические исключения OutOfMemoryExeptions в своем приложении (ссылка) и этот метод всегда присутствует в трассировке стека. За что отвечает? - person BabaBooey; 23.02.2011
comment
@Tim - не слишком много знаю об этом, но я полагаю, что это какой-то метод, который работает в потоке рендеринга, который отвечает за обновление положения элементов при изменении их значений. - person RandomEngy; 25.02.2011

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

person Drew Marsh    schedule 12.10.2009
comment
забавно, вы должны упомянуть об этом. Это именно то, с чего я начал, на самом деле мой первый вопрос был именно таким, поэтому я добавлю его обратно. - person discorax; 12.10.2009
comment
Спасибо за добавление этой детали. Таким образом, вы просто меняете значение углов, я очень сомневаюсь, что это тоже вызывает проблему. Можете ли вы изменить таймер так, чтобы он срабатывал каждую 10-ю или 100-ю долю секунды? Если это действительно имеет какое-то отношение к этому циклу таймера, это поможет ускорить процесс, чтобы его было намного проще воспроизвести. - person Drew Marsh; 13.10.2009
comment
Это хорошее предложение. Я сделал это, и это, кажется, ускорило проблему. Есть еще одна деталь, которую я каждый раз создаю экземпляр класса Clock, это конструктор, на который я ссылаюсь. Возможно, объект Clock неправильно собирается сборщиком мусора, что может быть причиной. - person discorax; 13.10.2009

hHandRT.Angle = _hAngle;
mHandRT.Angle = _mAngle;
sHandRT.Angle = _sAngle;

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

Вы устанавливаете свойство Angle вашего преобразования для всех 3 преобразований, даже если вам не нужно, чтобы они менялись каждую секунду. Ваша минута будет меняться на каждые 60 изменений, а ваш час будет меняться на каждые 3600 секунд. Однако вы можете по крайней мере уменьшить угол смены часов на каждую секунду.

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

Подробный анализ:

Посмотрев на ваш код, я чувствую, что ваше событие DispatcherTimer_Tick слишком много вычисляет, помните, что поток Dispatcher уже перегружен множеством вещей, таких как управление маршрутизацией событий, визуальным обновлением и т. д., если ваш процессор больше загружен для выполнения пользовательской задачи в диспетчере поток, который тоже в каждом втором событии, он определенно будет продолжать увеличивать очередь ожидающих задач.

Вы можете подумать, что это небольшое вычисление умножения, но для потока Dispatcher это может быть дорогостоящим, когда речь идет о загрузке часовых поясов, преобразовании значения времени и т. д. Вы должны профилировать и видеть время выполнения тика.

Вы должны использовать объект System.Threading.Timer, который будет запускаться в другом потоке, после каждого события тика, когда вы закончите свои расчеты требуемых окончательных углов, вы можете передать их в поток Dispatcher.

как,

Dispatcher.BeginInvoke((Action)delegate(){
   hHandRT.Angle = _hAngle;
   mHandRT.Angle = _mAngle;
   sHandRT.Angle = _sAngle;   
});

Делая это, вы немного снизите нагрузку на поток диспетчера.

person Akash Kava    schedule 14.10.2009
comment
Я согласен, что производительности помогло бы, если бы не выполнялась вся работа над потоком диспетчера, но, похоже, это не решает напрямую проблемы исходного сообщения. В долгосрочной перспективе это может быть хорошей идеей, но я подозреваю, что на изменение углов на самом деле больше накладных расходов, чем на математику даты, которая происходит выше, поэтому это может быть немного чрезмерной оптимизацией. - person Drew Marsh; 14.10.2009
comment
Вы можете каким-то образом профилировать и узнать длину очереди объекта-диспетчера, но PriorityQueue объекта-диспетчера является закрытым членом, поэтому я не знаю, как его увидеть. Но я уверен, что более длительные сроки могут быть лучшей идеей, потому что вы, безусловно, загружаете в очередь диспетчера больше, чем он может обработать. Я много работал с потоками диспетчера, иногда даже 2-3 миллиона секунд кода в другом потоке не имеют большого значения, вы не заметите, пока он не накапливается в течение нескольких часов. - person Akash Kava; 14.10.2009
comment
Объем работы, выполняемой в этом примере в методе Dispatcher, абсолютно тривиален. Я смоделировал аналогичную программу, и событие запускалось каждую миллисекунду, а средняя загрузка ЦП программой оставалась ниже 1%. Я согласен с Дрю: сосредоточение внимания на использовании ЦП метода тиков является преждевременной оптимизацией. - person RandomEngy; 15.10.2009