SetTimer () подводные камни - PullRequest
7 голосов
/ 05 мая 2011

У меня есть таймер без окон (без WM_TIMER), который запускает функцию обратного вызова только один раз по истечении заданного периода времени.Это реализовано как SetTimer()/KillTimer().Периоды времени достаточно малы: 100-300 миллисекунд.

Достаточно ли дешево (я имею в виду производительность) для вызова пары SetTimer()/KillTimer() для каждого такого короткого интервала времени?

Что если у меня есть 100 таких таймеров, которые периодически вызывают SetTimer()/KillTimer()?Сколько объектов таймера Window может существовать одновременно в системе?

Вот в чем вопрос: используйте набор таких объектов таймера и полагайтесь на хорошую реализацию таймеров в Windows, или создайте один объект таймера Windows, который помечает каждый, скажем,, 30 миллисекунд, и подпишитесь на все пользовательские одноразовые таймеры 100-300 миллисекунд.

Спасибо

Ответы [ 3 ]

2 голосов
/ 04 февраля 2014

Самая большая ошибка SetTimer() заключается в том, что на самом деле это объект USER (несмотря на то, что он не указан в списке объектов MSDN USER ), следовательно, он падаетв Windows ограничение объектов USER - по умолчанию максимум 10000 объектов на процесс, максимум 65535 объектов на сеанс (все запущенные процессы).

Это легко проверить с помощью простого теста - просто вызовите SetTimer() (параметры не имеют значения, как оконные, так и безоконные ведут себя одинаково) и видят увеличение количества объектов USER в диспетчере задач.

Также см. ReactOS ntuser.h source и эта статья .Оба они утверждают, что TYPE_TIMER является одним из типов дескрипторов USER.

Так что будьте осторожны - создание нескольких таймеров может привести к исчерпанию системных ресурсов и сбоя процесса или даже всей системы.

2 голосов
/ 05 мая 2011

Проблема с сообщениями таймера, когда вы пытаетесь их использовать, заключается в том, что они являются сообщениями с низким приоритетом.На самом деле они являются поддельными сообщениями.Таймеры связаны с базовым объектом таймера ядра - когда цикл сообщений обнаруживает этот пожар, он просто помечает очередь сообщений текущих потоков флагом, указывающим, что следующий вызов GetMessage - когда нет других сообщений для обработки - должен синтезировать WM_TIMER.сообщение как раз вовремя и верните его.

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

Если вы управляете циклом сообщений, вы можете использовать свой собственный список событий таймера (вместе с метками времени GetTickCount, когда они должны происходить) и MSGWaitForMultipleObject - вместо GetMessage ждать сообщения.Используйте параметр dwTimeout, чтобы указать наименьший интервал - с сегодняшнего дня - до следующего таймера.Поэтому он будет возвращаться из ожидания сообщений каждый раз, когда у вас есть таймер для обработки.

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

0 голосов
/ 28 ноября 2017

Вот детали, которые, на мой взгляд, вы на самом деле ищете, задавая этот вопрос:

SetTimer () сначала просканирует список неядерных таймеров (двусвязный список), чтобы определить, существует ли идентификатор таймера. Если таймер существует, он просто будет сброшен. Если нет, то вызов HMAllocObject происходит и создает пространство для структуры. Структура таймера будет затем заполнена и связана с заголовком списка.

Это будет общая нагрузка на создание каждых 100 ваших таймеров. Это именно то, что делает подпрограмма, за исключением проверки параметров min и max dwElapsed.

Что касается истечения срока действия таймера, список таймеров сканируется с (приблизительно) длительностью наименьшей длительности таймера, наблюдаемой во время последнего сканирования списка таймеров. (На самом деле, на самом деле происходит следующее: таймер ядра устанавливается на длительность наименьшего из найденных пользовательских таймеров, и этот таймер ядра активирует поток, выполняющий проверку истечений таймера пользователя, и вызывает соответствующие потоки, устанавливая флаг в статус их очереди сообщений.)

Для каждого таймера в списке текущая дельта между последним (в мс) списком таймеров и текущим временем (в мс) уменьшается с каждого таймера в списке. Когда он требуется (осталось <= 0), он помечается как «готовый» в своей собственной структуре, и указатель на информацию о потоке считывается из структуры таймера и используется для пробуждения соответствующего потока путем установки флага потока QS_TIMER. Затем он увеличивает счетчик CurrentTimersReady в очереди сообщений. Это все, что делает истечение таймера. Фактические сообщения не опубликованы. </p>

Когда ваш основной обработчик сообщений вызывает GetMessage (), когда другие сообщения недоступны, GetMessage () проверяет наличие QS_TIMER в битах пробуждения вашего потока, а если установлено - генерирует сообщение WM_TIMER, сканирует полный пользовательский таймер list для самого маленького таймера в списке, помеченного как READY и связанного с вашим идентификатором потока. Затем он уменьшает ваш поток CurrentTimersReady count, и если 0, очищает бит пробуждения таймера. Ваш следующий вызов GetMessage () будет вызывать то же самое, пока все таймеры не будут исчерпаны.

Таймеры одного выстрела остаются инстанцированными. Когда они истекают, они помечаются как ожидающие. Следующий вызов SetTimer () с тем же идентификатором таймера просто обновит и повторно активирует оригинал. Оба таймера и один выстрел сбрасываются самостоятельно и умирают только с KillTimer или когда ваш поток или окно разрушены.

Реализация Windows очень проста, и я думаю, что для вас было бы тривиально написать более производительную реализацию.

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