Ожидающие таймеры (Waitable Timer )

Ожидающие таймеры

Ожидающие таймеры - объекты ядра, которые предназначены для отсчета промежутков времени. Окончание временного интервала определяется по переходу таймера в свободное состояние (signaled). Момент перехода таймера в свободное состояние определяется одной из ожидающих функций. По аналогии с радиоэлектроникой, ожидающий таймер можно представить как мультивибратор, работающий в одном из двух режимов, ожидающим запуска от внешнего сигнала или автогенераторном, когда формируется непрерывная последовательность импульсов. Основное применение ожидающих таймеров - синхронизация потоков в многопоточном программировании.

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

Для создания и управления ожидающими таймерами существует несколько функций:

Если поток, который установил таймер, уничтожается, то таймер останавливается без изменения состояния, но не уничтожается вместе с потоком. Если есть функция APC, она уничтожается, в противном случае уничтожение потока не оказывает влияния та таймер. Таймер с ручной установкой после перехода в свободное состояние остается в нем до сброса функцией SetWaitableTimer. Это касается и периодического таймера, таким образом периодический таймер с ручной установкой фактически не является периодическим таймером. В MSDN не поясняется каким образом можно полностью уничтожить ожидающий таймер, можно понимать, что не именованный таймер будет уничтожен после вызова CloseHandle, а именованный, когда последний поток вызовет CloseHandle, во всяком случае логически должно быть так.

Рассмотрим часто встречающуюся задачу, для решения которой необходимо использовать ожидающий таймер. Задача заключается в сборе информации от внешнего устройства, ее последующей обработки и отображении. Центральный компьтер должен задать начало цикла для подчиненного устройства и через некоторое время получить информацию от него. Для большей конкретизации и приближения к работе ожидающего таймера рассмотрим ситуацию, когда внешнее устройство производит рабочий цикл по своей программе(микропроцессор) и через известное время от него нужно получить данные. Начало цикла задает центральный компьютер, а через определенное время он же забирает данные. Линия связи управляется собственным драйвером и исключена из задачи. Организовать такой цикл можно с помощью ожидающего таймера как показано на рисунке 1.


Рис. 1

Задача будет решена если перед переводом рабочего потока в спящее состояние (точка 2,4) послать сигнал внешнему устройству на выполнение рабочего цикла, а после срабатывания таймера (точка 3) произвести загрузку данных. Загрузка данных может быть произведена в функции APC или в прямо в пробудившемся потоке.

В одной из программ мне пришлось использовать ожидающий таймер так как описано выше. Уже потом, читая книгу Рихтера "Windows via C++" , мне попались такие строки в главе, посвященной синхронизации потоков: "Как-то раз мне понадобилось, чтобы один из потоков в пуле (управляемом через порт завершения ввода-вывода) пробуждался по таймеру через определенные интервалы времени. К сожалению, такую функциональность ожидаемые таймеры не поддерживают. Для решения этой задачи мне пришлось создать отдельный поток, который всего-то и делал, что настраивал ожидаемый таймер и ждал его освобождения. Когда таймер переходил в свободное состояние, этот по ток вызывал PostQueuedCompletionStatus, передавая соответствующее уведомление потоку в пуле". К сожалению, в приложении к книге по этой теме нет примера.

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

Класс достаточно простой и обеспечивает функционирование только таймера с автосбросом, его функциональность можно легко расширить. Класс CWaitTimer и содержит всего несколько функций:

Файл waittimer.h можно скачать здесь. Если Вам потребуются дополнительные материалы, обратитесь к автору с письмом (dslev@yandex.ru).

Примечание.
Применение объектов ядра для синхронизации потоков имеет некоторые тонкости, которые следует учитывать. Особенно это касается взаимодействия с аппаратной частью и связано с прекращением работы потока. Обычно рекомендуется прекращать работу потока естественным образом при выходе из функции потока, в этом случае автоматически удаляются все части потока, в том числе и объекты, созданные в потоке. Функцию TerminateThread не рекомендуется использовать, поскольку она не всегда удаляет все объекты потока. Поэтому выход из потока, работающего в цикле, лучше осуществлять путем установки флага, который будет анализироваться потоком. Это конечно, дополнительный код, но безопасный путь. Установка флага из другого потока должна быть синхронизирована с рабочим потоком, который периодически анализирует его состояние. Для этой цели очень удобны функции Interlockxxx. Иногда при взаимодействии с аппаратной частью были замечены случаи, когда поток не завершался немедленно после анализа флага, а ожидал завершения работы объекта ядра, который взаимодействовал с драйвером устройства. В результате выход из функции потока производился дважды и оба раза завершался с невразумительными ошибками, которые требуют глубокого анализа. В таких случаях следует все-таки использовать TerminateThread, предварительно удалив вручную не удаляемые объекты.

[в начало]

Hosted by uCoz