Ожидаемые таймеры и АРС-очередь
Теперь Вы знаете, как создавать и настраивать таймер. Вы также научились приоста навливать потоки на таймере, передавая его описатель в 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го из «тревожного» состояния, и вызова АРС-функции не последует. Правда, АРС-функции редко используются совместно с ожидаемыми таймерами, так как всегда можно дож даться перехода таймера в свободное состояние, а затем сделать то, что нужно.