Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
375
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

//знала: нам нужно относительное, а не абсолютное время 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 автоматически отменяет пре дыдущие настройки перед установкой новых

Ожидаемые таймеры и АРС-очередь

Теперь Вы знаете, как создавать и настраивать таймер. Вы также научились приоста навливать потоки на таймере, передавая его описатель в WaitForSingleObjects или Wait ForMultipleObjects. Однако у Вас есть возможность создать очередь асинхронных вы зовов процедур (asynchronous procedure call, APC) для потока, вызывающего SetWai tableTimer в момент, когда таймер свободен.

Обычно при обращении к функции SetWaitableTtmer Вы передаете NULL в пара метрах pfnCompletionRoutine и pvArgToCompletionRoutine. В этом случае объект-таймер переходит в свободное состояние в заданное время. Чтобы таймер в этот момент поместил

вочередь вызов АРС-функции, нужно реализовать данную функцию и пе редать ее адрес

вSetWaitableTimer. АРС-функция должна выглядеть примерно так

VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompleUonRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue)

{

// здесь делаем то, что нужно

}

Я назвал эту функцию TimerAPCRoutine, по Вы можете назвать ее как угодно. Она вызывается из того потока, который обратился к SetWaitableTimer в момент срабаты вания таймера, — но только если вызывающий поток находится в «тревожном» (aler table) состоянии, т. e. ожидает этого в вызове одной из функций SleepEx, WaitForSingle ObjectEx, WaitForMultipleObjectsEx,MsgWaitForMultipleObjectsEx или SignalObjectAndWait Если же поток этого не ожидает в любой из перечисленных функций, система не

поставит в очередь АРС-функцию таймера. Тем самым система не даст АРС-очереди потока переполниться уведомлениями от таймера, которые могли бы впустую израс ходовать колоссальный объем памяти.

Если в момент срабатывания таймера Ваш поток находится в одной из перечис ленных ранее функций, система заставляет его вызвать процедуру обратного вызова Первый ее параметр совпадает с параметром pvArgToCompletionRoutine, передаваемым в функцию SetWaitableTimer, Это позволяет передавать в TimerAPCRoutine какие-либо данные (обычно указатель на определенную Вами структуру) Остальные два парамет ра, dwTimerLowValue и dwTimerHighValue, задают время срабатывания таймера. Код, приведенный ниже, демонстрирует, как принять эту информацию и показать ее поль зователю.

VOID APIENTRY TimerAPCRoutine(PVOID pvArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwT:merHighValue)

{

FILETIME ftUTC, ftLocal;

SYSTEMTIME st;

TCHAR szBuf[256];

// записываем время в структуру

FILETIME ftUTC.dwlowDateTime = dwTimerLowValue; ftUTC.dwHighDateFime = dwTimerHighValue;

//преобразуем UTC-время в местное

FileTimeToLocalFileTime(&ftUTC, &ftLocal);

//преобразуем структуру FILETIME в структуру SYSTEMTIME,

//как того требуют функции GetDateFormat и GetTimeFormat FileTimetoSystemTime(&ftLocal, &st);

//формируем строку с датой и временем, в которой

//сработал таймер

GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, szBuf, sizeof(szBuf) / sizeof(TCHAR));

_tcscat(szBuf, __TEXT(' '));

GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, _tcschr(szBuf, 0), si/eof(szBuf) / sizeor(TCHAR) - _tcslen(sz8uf));

// показываем время пользователю

MessageBox(NULL, szBuf, "Timer went off at ... ", MB_OK); }

Функция «тревожного ожидания" возвращает управление только после обработ ки вссх элементов АРС-очереди. Поэтому Вы должны позаботиться о том, чтобы Ваша функция TimerAPCRoutine заканчивала свою работу до того, как таймер вновь подаeт сигнал (перейдет в свободное состояние). Иначе говоря, элементы не должны ставить ся в АРСочередь быстрее, чем они могут быть обработаны.

Следующий фрагмент кода показывает, как правильно пользоваться таймерами и APC:

void SomeFunc() {

//создаем таймер (его тип не имеет значения)

HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL);

// настраиваем таймер на срабатывание через 5 секунд

LARGE_INTEGER li = { 0 };

SetWaitableTimer(hTimer, &li, 5000, TimerAPCRoutine, NULL, FALSE);

// ждем срабатывания таймура в "тревожном" состоянии

SleepEx(INFINITE, TRUE);

CloseHandle(hTimer);

}

И последнее. Взгляните ни эют фрагмент кода:

HANDLE hTimer - CreateWaitableTimer(NULL, FAISE, NULL);

SetWaitableTimer(hTimer, ..., TimerAPCRoutine, );

WaitForSingleObjectEx(hTimer, INFINITE, TRUE);

Никогда ие пнигите такой код, потому что вызов WaitForSingleObjectEx на деле за ставляет дважды ожидать таймер — по описателю hTimer и в «тревожном" состоянии Когда таймер перейдет в свободное состояние, поток пробудится, что выведет eго из «тревожного» состояния, и вызова АРС-функции не последует. Правда, АРС-функции редко используются совместно с ожидаемыми таймерами, так как всегда можно дож даться перехода таймера в свободное состояние, а затем сделать то, что нужно.

И еще кое-что о таймерах

Таймеры часто применяются в коммуникационных протоколах. Например, ссли кли ент делает запрос серверу и тот не отвечает в течение определенного времени, кли ент считает, что сервер не доступен. Сегодня клиентские машины взаимодействуют, как правило, со множеством серверов одновременно. Если бы объект ядра «таймер» создавался для каждого запроса, производительность системы снизилась бы весьма заметно. В большинстве приложений можно создавать единственный объект-таймер и по мере необходимости просто изменять время его срабатывания.

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