Как сделать таймер в делфи 7

Обновлено: 06.07.2024

Мысль о хорошем таймере давно волнует умы программистов. Сразу оговорюсь, что речь не идет о прецизионном, "высокочастотном" иструменте отсчета интервалов времени, с дискретностью 1 мс и менее, как иногда хочется. Для этого существуют иные методы и/или иные операционные системы.

Здесь же будет построен просто надежный таймер общего назначения, который "тикнет" вовремя, во что бы то ни стало. Реализация в пределах стандартных возможностей Win32API, т.е. ничего "военного". Плюс одна интересная идея, заимствованная из мира Unix.

Стандартные средства

Да чем же они плохи, эти User таймеры? А вот чем!

Дискретность временного интервала оставляет желать лучшего. В Win9x она составляет 55 мс. Но даже в NT интервал квантуется не менее чем по 10 мс.

Альтернатива

Но, к сожалению, эти функции реализованы только в Windows NT/2000 и, следовательно не подходят для программы, рассчитанной на любую платформу Win32.

Что же, все-таки, можно сделать

Функция Sleep имеет дискретность отработки заданного интервала (по результатам эксперимента) 10 мс в Windows NT и примерно 3-4 мс в Windows 98. Во многих случаях достаточно просто вызвать эту функцию там, где нужна задержка, если потоку больше нечем заняться в течение этого интервала времени.

Цикл в отдельном потоке с вызовом Sleep с постоянным интервалом и опросом списка таймерных объектов (у каждого свой интервал, заданный клиентом) с определением момента срабатывания, позволяет обрабатывать столько виртуальных таймеров, сколько потребуется программе. Высокий приоритет, заданный потоку, даст возможность таймерам "тикать" даже тогда, когда другие потоки заняты работой.

Использование библиотеки классов, инкапсулирующих системные средства межпоточного взаимодействия (см. статью "Набор объектов-нотификаторов") позволит клиентам выбирать наиболее подходящий для конкретного случая способ извещения.

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

В начале 90-х годов прошлого века я занимался разработкой контроллеров на i8051 и софта для них (макроассемблер 2500 A.D.). И был тогда сделан "драйвер виртуальных таймеров", расширяющий возможности однокристалки (у нее всего два аппаратных таймера) по обеспечению программы инструментами отсчета времени. Будильников там еще не было. Работа велась в обработчике аппаратного прерывания.

В 1993 году в составе программы верхнего уровня системы учета энергоресурсов в среде DOS (Turbo-Pascal), в разработке которой я участвовал, был таймерный менеджер (тот самый TIMERMAN). Он предоставлял набор интервальных таймеров и ежесуточных будильников, имея обработчики прерываний стандартного таймера ($1C) и будильника RTC ($4A). Интервал в секундах до 65535. Обработка таймеров выполнялась, когда менеджер получал управление в общем цикле программы (была организована кооперативная многозадачность между модулями). Клиент мог сам проверять таймер или передать адрес своей процедуры — натуральный callback. Позднее, с переходом на BP7 и protected mode, менеджер перекочевал в независимую DLL.

В 1997 году Timerman был портирован под OS/2 (Virtual Pascal) без изменений в архитектуре — только прерывание было заменено на Thread.

В 1999 году в связи с разработкой системы учета под Windows CE был разработан заново таймерный менеджер, и был он в виде DLL. Практически это было то, что я сейчас предлагаю, только реализация на VC++ (без использования MFC). В том же году Timerman.dll был переписан на Delphi в современном виде.

  1. интервальный таймер (одновибратор/мультивибратор);
    • точность — 10 миллисекунд;
    • управление: пуск, останов, задание периода и режима.
  2. синхронизированный таймер (будильник), привязан к системному времени;
    • набор моментов срабатывания конфигурируется строкой в формате CRON, позволяющем простым способом описывать сложные периодические события;
    • дискретность настройки — от секунды до месяца;
    • управление: пуск, останов, задание маски времени и режима.

Реализовано все это в виде DLL — для возможности использования не только в программах на Delphi. Впрочем, можно использовать Subj просто как библиотеку классов — модуль Timers.pas. При желании можно натянуть на это дело компонентную крышу, но у меня такой необходимости не возникало. В нынешнем виде его можно использовать в программах как с формами, так и вообще без "морды", т.к. он не использует VCL.

Разработано и отлажено в среде Delphi 5, но будет компилироваться и в более ранних - может понадобиться замена типа dword на что-нибудь похожее (беззнаковость здесь роли не играет).

Все исходные тексты и откомпилированная DLL собраны в архив timerman.zip.
Тестовая программа (исходные тексты) отдельно в файле tmdemo.zip
Для интересующихся — сорцы версии на С++ в файле tmmancpp.zip.

Здесь приведен текст модуля импорта для использования Timerman.dll.

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

Строка CRON представляет собой несколько списков чисел, разделенных пробелом. Каждый список задает перечень моментов времени или даты, в единицах, зависящих от позиции (номера) списка в строке.

Последовательность списков в строке CRON такова:

Если какая-либо единица времени/даты имеет произвольное значение, то ее просто опускают (если все старшие единицы тоже произвольны) или список ее значений представляют знаком " * " (если соседняя старшая единица задана).

Примеры записи периодических событий в формате CRON (с вариантами):

Строка списка без пробелов представляет собой набор групп чисел, разделенных запятой " , ". Группа может состоять из одного числа или диапазона . Последний задается двумя числами, разделенными дефисом " - " (начальное и конечное значение). Опционально после диапазона может стоять значение шага , отделенное знаком плюс " + ". По-умолчанию шаг равен 1. Если шаг указан, то конечное значение можно опустить — тогда оно по-умолчанию будет равно максимальному значению в контексте назначения данного списка. Начало диапазона по-умолчанию равно 0.

Символ звездочки " * " вместо группы означает весь диапазон возможных значений в данном контексте. Порядок следования групп в строке списка роли не играет.

Пример. Cписок вида: 0-5,8,12,20-30+2
интерпретируется как последовательность: 0,1,2,3,4,5,8,12,20,22,24,26,28,30

Как и IntervalTimer, FixedTimer может работать в периодическом и старт-стопном режиме (параметр Mode=tmPeriod,tmStartStop в tmCreateFixedTimer ). Но имеется еще дополнительная опция "уверенной синхронизации" ( Mode=tmSureSync ). В этом режиме производится проверка на пропуск предыдущего момента срабатывания. При этом, если даже в один прекрасный момент что-то помешало таймеру "тикнуть" (в течение более 1 с поток таймерного менеджера не получал управление), в следующую секунду он обязательно сработает "за тот раз". Время последнего срабатывания запоминается, его можно прочитать и установить.

Разумеется, в мире нет ничего абсолютного. А тем более когда дело касается столь нереалтаймовой системы как Уиндоус. Впрочем, в случае real-time OS вопрос о таймерах вообще бы не стоял. А в наших условиях вполне может найтись в системе какой-нибудь хулиганский поток с высоким приоритетом (выше или равным нашему), который наглым образом будет отбирать управление на длительное время (больше длительности внутреннего цикла таймерного менеджера — 10 мс). И тогда наш таймер будет пропускать "тики" на коротких заданных интервалах и увеличивать погрешность на длинных. Это происходит в том случае, если кто-то работает не по правилам, либо производительности процессора не хватает для работы системы.

В целях проверки и демонстрации функционирования таймерного менеджера, а также сравнительного анализа со стандартным таймером была разработана демо-программа (tmdemo.zip).

Проведенные исследования показали (см. таблицу), что интервальный таймер ведет себя почти идеально от 100 мс и достаточно хорошо на более мелких интервалах, тогда как стандартный таймер на коротких интервалах, а особенно под нагрузкой совсем сдает позиции. На интервале 10 мс интенсивная обработка извещений от таймеров (обновление контролов, особенно TMemo) приводит к 100% загрузке процессора. Синхронизированный таймер (FixedTimer), заряженный на минимальный интервал 1 с, всегда давал точное число тиков, причем срабатывал в начале секунды с небольшим разбросом.

Результаты приведены для следующей конфигурации: Cyrix 6x86PR233/64M/WinNT4. Измерения проводились также на платформе Win98SE, где IntervalTimer показал примерно те же результаты, а TTimer еще более худшие.

Исходные тексты программ, приложенные к данной статье, распространяются на правах freeware.

  • Все исходные тексты и откомпилированная DLL (49.5 K) обновление от 7/5/2005 7:56:00 AM
  • Тестовая программа (исходные тексты) (5 K) обновление от 7/18/2001
  • Для интересующихся - сорцы версии на С++ (18.6 K) обновление от 7/18/2001


Смотрите также материалы по темам: [TMemo] [TTimer] [Таймеры]

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

Читайте также: