Windows для профессионалов


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


Ожидаемые таймеры (waitahle timers) ~ это объекты ядра, которые самостоятельно переходят в свободное состояние в определенное время или через регулярные про межутки времени. Чтобы создать ожидаемый таймер, достаточно вызвать функцию CreateWaitableTimer.

HANDLE CreateWaitableTimer( PSECURITY_ATTRIBUTES psa, BOOL fManualReset, PCTSTR pszName);

О параметрахр psa и pszName я уже рассказывал в главе 3. Разумеется, любой про цесс может получить свой ("процессо-зависимый") описатель существующего объек та "ожидаемый таймер", вызвав OpenWaitableTimer.

HANDLE OpenWaitableTirrer( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName);

По аналогии с событиями параметр fManualReset определяет тип ожидаемого тай мера: со сбросом вручную или с автосбросом. Когда освобождается таймер со сбро сом вручную, возобновляется выполнение всех потоков, ожидавших этот объект, а когда в свободное состояние переходит таймер с автосбросом — лишь одного из потоков.

Объекты "ожидаемый таймер" всегда создаются в занятом состоянии. Чтобы со общить таймеру, в какой момент он должен перейти в свободное состояние, вызови те функцию SetWaitableTimer.

BOOL SetWaitableTimer( HANDLE hTimer, const LARGE_INTEGER *pDueTime, LONG lPeriod, PTIMERAPCROUTINE pfnCompletionRoutine, PVOID pvArgToCotnpletionRoutine, BOOI fResume);

Эта функция принимает несколько параметров, в которых легко запутаться Оче видно, что hTimer определяет нужный таймер. Следующие два параметра (pDиеТiте и lPeriod) используются совместно, первый из них задает, когда таймер должен сра ботать в первый раз, второй определяет, насколько часто это должно происходить в дальнейшем. Попробуем для примера установить таймер так, чтобы в первый раз он сработал 1 января 2002 года в 1:00 PM, а потом срабатывал каждые 6 часов.

// объявляем свои локальные переменные

HANDLE hTimer;
SYSTEMTIME st;
FILETIME ftLocal, ftUTC;
LARGE_INTEGER liUTC;

// создаем таймер с автосбросом
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);


// таймер должен сработать в первый раз 1 января 2002 года в 1:00 PM
// но местному времени



st.wYear = 2002; // год
st.wMonth = 1; // январь
st.wOayOfWeek = 0; // игнорируется
st.wDay = 1, // первое число месяца
st.wHour = 13; // 1 PM
st.wMinute = 0; // 0 минут
st.wSecond = 0, // 0 секунд
st.wMilliseconds = 0; // 0 миллисекунд

SystemTimeToFileTime(&st, &ftLocal);

// преобразуем местное время в UTC-время
LocalFileTimeToFilelime(&ttLocal, &ftUTC);

// преобразуем FILETIME в LARGE_INTEGER из-за различий в выравнивании данных
liUTC.LowPart = ftUTC dwLowDateTime;
liUTC.HighPart = ftUTC dwHighDateTime;

// устанавливаем таймер
SetWaitablcTimer(hTimer, &liUTC, 6 * 60 * 60 * 1000, NULL, NULL, FALSE);

...

Этот фрагмент кода сначала инициализирует структуру SYSTEMTIME, определяя время первого срабатывания таймера (его перехода в свободное состояние). Я уста новил это время как местное. Второй параметр представляется как const LARGE_IN TEGER * и поэтому нс позволяет напрямую использовать структуру SYSTEMTIME. Од нако двоичные форматы структур FILETIME и LARGE_INTEGER идентичны: обе содер жат по два 32-битных значения. Таким образом, мы можем преобразовать структуру SYSTEMTIME в FILETIME. Другая проблема заключается в том, что функция SetWaitable Timer ждет передачи времени в формате UTC (Coordinated Universal Time). Нужное преобразование легко исуществляется вызовом LocalFileTimeToFileTime

Поскольку двоичные форматы структур FILETIMF, и IARGE_INTEGER идентичны, у Вас может появиться искушение передать в SetWaitableTimer адрес структуры FILETIME напрямую;

// устанавливаем таймер
SetWaitableTimer(hTirner, (PLARGE^INTEGER) &ftUTC, 6 * 60 * 60 * 1000, NULL, NULL, FALSE);

В сущности, разбираясь с этой функцией, я так и поступил. По это большая ошиб ка! Хотя двоичные форматы структур FILETIME и LARGE_INTEGER совпадают, вырав нивание этих структур осуществляется по-разному. Адрес любой структуры FILETIME должен начинаться на 32-битной границе, а адрес любой структуры IARGE_INTEGER — на 64-битной.


Вызов SetWaitableTimer с передачей ей структуры FILETIME может cpa

ботать корректно, но может и не сработать — все зависит от того, попадет ли начало структуры FlLETIME на 64-битную границу. В то же время компилятор гарантирует, что структура LARGE_INTEGER всегда будет начинаться на 64-битной границе, и по этому правильнее скопировать элементы FILETIME в элементы LARGE_INTEGER, а за тем передать в SetWaitableTtmer адрес именно структуры LARGE_INTEGER.

NOTE:
Процессоры x86 всегда "молча" обрабатываю ссылки на невыровненные дан ные. Поэтому передача в SetWaitableTimer адреса структуры FILETIME будет сра батывать, если приложение выполняется на машине с процессором x86 Од нако другие процессоры (например, Alpha) в таких случаях, как правило, ге нерируют исключение EXCEPTION_DATATYPE_MISALIGNMENT, которое приво дит к завершению Вашего процесса Ошибки, связанные с выравниванием дан ных, — самый серьезный источник проблем при переносе на другие процес сорные платформы программного кода, корректно работавшего на процессо рах x86 Так что, обратив внимание на проблемы выравнивания данных сей час, Вы сэкономите себе месяцы труда при переносе программы на другие платформы в будущем! Подробнее о выравнивании данных см. главу 13.

Чтобы разобраться в том, как заставить таймер срабатывать каждые 6 часов (на чиная с 1:00 PM 1 января 2002 года), рассмотрим параметр lPeriod функции SetWaitable Timer. Этот параметр определяет последующую частоту срабатывания таймера (в мс). Чтобы установить 6 часов, я передаю значение, равное 21 600 000 мс (т e. 6 часов * 60 минут • 60 секунд • 1000 миллисекунд).

О последних трех параметрах функции SetWaitableTimer мы поговорим ближе к концу этого раздела, а сейчас продолжим обсуждение второго и третьего парамет ров Вместо того чтобы устанавливать время первого срабатывания таймера в абсо лютных единицах, Вы можете задать его в относительных единицах (в интервалах по 100 нс), при этом число должно быть отрицательным. (Одна секунда равна десяти миллионам интервалов по 100 нс.)



Следующий код демонстрирует, как установить таймер на первое срабатывание через 5 секунд после вызова SetWaitableTimer.

//объявляем свои локальные переменные
HANDLF hTimer;
LARGE_INTEGER li;

// создаем таймер с автосбросом
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

// таймер должен сработать через 5 секунд после вызова SetWaitableTimer;
// задаем время в интервалах по 100 нс
const int nTimerUnitsPerSecond = 10000000;

// делаем полученное значение отрицательным, чтобы SetWaitableTimer
// знала: нам нужно относительное, а не абсолютное время li.
QuadPart = -(5 * nTimerUnitsPerSecond);

// устанавливаем таймер (он срабатывает сначала через 5 секунд,
// а потом через каждые 6 часов)
SetWaitableTimer(hTimer, &li, 6 * 60 * 60 * 1000, NULL, NULL, FALSE);

...

Обычно нужно, чтобы таймер сработал только раз — через определенное (абсо лютное или относительное) время перешел в свободное состояние и уже больше никогда не срабатывал Для этого достаточно передать 0 в параметре lPeriod Затем можно либо вызвать CloseHandle, чтобы закрыть таймер, либо перенастроить таймер повторным вызовом SetWattableTimer с другими параметрами

И о последнем параметре функции SetWaitableTimer — lResume. Он полезен на компьютерах с поддержкой режима сна. Обычно в нем передают FALSE, и в приведен ных ранее фрагментах кода я тоже делал так. Но если Вы, скажем, пишете програм му-планировщик, которая позволяет устанавливать таймеры для напоминания о зап ланированных встречах, то должны передавать в этом параметре TRUE Когда таймер сработает, машина выйдет из режима сна (если она находилась в нем), и пробудятся потоки, ожидавшие этот таймер. Далее программа сможет проиграть какой-нибудь WAV-файл и вывести окно с напоминанием о предстоящей встрече. Если же Вы пере дадите FALSE в параметре fResume, объект-таЙмер перейдет в свободное состояние, но ожидавшие его потоки не получат процессорное время, пока компьютер не выйдет из режима сна

Рассмотрение ожидаемых таймеров было бы неполным, пропусти мы функцию CancelWaitable Timer.

BOOL CancelWaitableTimer(HANDLE hTimer);

Эта очень простая функция принимает описатель таймера и отменяет его (тай мер), после чего тот уже никогда не сработает, — если только Вы не переустановите его повторным вызовом SetWaitableTimer. Кстати, если Вам понадобится перенастро ить таймер, то вызывать CancelWattableTimer перед повторным обращением к SetWai tableTimer не требуется; каждый вызов SetWaitableTimer автоматически отменяет пре дыдущие настройки перед установкой новых


Содержание раздела