В Windows 2000 появился новый объект – очередь таймеров. Он основан на объекте исполнительной системы "ожидающий таймер", так что в качестве механизма обратного вызова используется APC. Создать объект можно с помощью следующей функции:
HANDLE CreateTimerQueue(VOID); |
Она возвращает хендл объекта "очередь таймеров" (queues of timers). После создания очереди в нее можно добавлять новые таймеры. Для этого используется функция CreateTimerQueueTimer:
BOOL CreateTimerQueueTimer( // указатель на хендл таймера PHANDLE phNewTimer, // хендл очереди таймеров HANDLE TimerQueue, // функция обратного вызова WAITORTIMERCALLBACK Callback, // параметр для функции обратного вызова PVOID Parameter, // время задержки перед первым вызовом в милисекундах DWORD DueTime, // период в милисекундах DWORD Period, // флаги ULONG Flags ); |
Рассмотрим параметры этой функции. Первый параметр возвращает хендл таймера, который можно использовать для вызова функций изменения таймера или его удаления (о них позже). Второй параметр – хендл очереди, созданной функцией CreateTimerQueue. В качестве него можно указать нулевое значение. В этом случае по умолчанию таймер будет добавлен к объекту "очередь таймеров". Третий параметр – адрес функции, которая будет вызвана при переходе таймера в сигнальное состояние. Вот ее прототип:
VOID CALLBACK WaitOrTimerCallback( PVOID lpParameter, // произвольныйпараметрBOOLEAN TimerOrWaitFired // причина вызова); |
Произвольный параметр для нее указывается в функции CreateTimerQueueTimer четвертым параметром. Параметр TimerOrWaitFired для таймеров всегда равен TRUE.
Пятый параметр определяет, сколько времени в миллисекундах пройдет до первого вызова функции WaitOrTimerCallback. Если указать 0, то эта функция будет вызвана практически сразу (примерно так же быстро, как и в случае QueueUserWorkItem).
Шестой параметр задает период вызова пользовательской функции. В качестве этого параметра можно указать нулевое значение, тогда функция WaitOrTimerCallback будет вызвана только один раз.
В качестве флагов функции CreateTimerQueueTimer можно указывать все флаги из таблицы 2 и два новых:
Константа | Значение | Описание |
WT_EXECUTEINTIMERTHREAD | 0x20 | Пользовательская функция вызывается в потоке таймера |
WT_EXECUTEONLYONCE | 8 | Пользовательская функция вызывается только один раз |
Таблица 6. Флаги функции CreateTimerQueueTimer.
Если ваша функция WaitOrTimerCallback очень быстро отрабатывает, а количество запросов невелико – лучше всего указать флаг WT_EXECUTEINTIMERTHREAD. В этой ситуации функция будет вызвана в потоке, ожидающем таймера. Будьте осторожны – длительная блокировка пользовательской функции приведет к тому, что ожидающий поток не сможет обрабатывать приходящие запросы.
При указании флага WT_EXECUTEONLYONCE таймер будет установлен в сигнальное состояние только один раз.
Если вам больше не нужен таймер, его можно удалить из очереди с помощью функции DeleteTimerQueueTimer.
BOOL DeleteTimerQueueTimer( // хендл очереди таймеров HANDLE TimerQueue, // хендл таймера HANDLE Timer, // хендл объекта, устанавливаемого в сигнальное состояние после удаления HANDLE CompletionEvent); |
Если используется очередь по умолчанию, в качестве первого параметра нужно передать NULL. Второй параметр – хендл удаляемого таймера. Третий параметр может принимать следующие значения:
INVALID_HANDLE_VALUE – означает, что вызывающая функция будет заблокирована до тех пор, пока таймер не обработает все текущие запросы. Вы должны быть осторожны с этим значением, так как вызов функции удаления таймера в самой пользовательской функции приведет к взаимоблокировке (deadlock).
NULL – если вы не хотите ожидать завершения обработки всех текущих запросов. Функция DeleteTimerQueueTimer возвратит управление немедленно.
Допустимый хендл объекта – если необходимо синхронизировать окончание обработки текущих запросов. Функция DeleteTimerQueueTimer возвратит управление немедленно, но после окончания обработки запросов объект завершения устанавливается в сигнальное состояние.
Можно сразу удалить всю очередь таймеров с помощью следующей функции:
BOOL DeleteTimerQueueEx( // хендл очереди таймеров HANDLE TimerQueue, // хендл объекта, устанавливаемого в сигнальное состояние после удаления HANDLE CompletionEvent); |
Если удаляется очередь по умолчанию, в качестве первого параметра нужно передать NULL. Второй параметр имеет то же значение, что и в предыдущей функции DeleteTimerQueueTimer.
Кроме создания и удаления таймера в очереди, можно изменять некоторые его характеристики. Это делается вызовом функции ChangeTimerQueueTimer.
BOOL ChangeTimerQueueTimer( HANDLE TimerQueue, // хендлочередитаймеровHANDLE Timer, // хендл таймера ULONG DueTime, // новое значение задержки перед вызовом ULONG Period // новое значение периода вызова); |
Функция вопросов не вызывает, однако нужно отметить, что она не оказывает влияния на «одноразовые» (one-shot) таймеры, при создании которых в качестве периода был указан 0.
Характеристика | Значение |
Начальное коли-чество потоков в пуле | 1 |
Когда поток удаляется | Когда удаляется последний таймер из очереди |
Способ ожидания, используемый потоком | Тревожное (alertable) ожидание |
Поток просыпается при | Приходе APC-запроса |
Таблица 7. Описание характеристик работы объекта "очередь таймеров".
Вызов функции при переходе объекта в сигнальное состояние
В приложении часто возникает необходимость дождаться какого-либо объекта и выполнить определенное действие. Для многопоточных приложений приходится для каждого такого случая заводить отдельный поток, что нехорошо. В этом случае на помощь приходит функция RegisterWaitForSingleObject. Она позволяет вызывать произвольную пользовательскую функцию после того, как заданный объект перейдет в сигнальное состояние. Причем никаких потоков создавать не нужно, все делается автоматически. Рассмотрим прототип этой функции:
BOOL RegisterWaitForSingleObject( PHANDLE phNewWaitObject, // адрес хендла объекта ожидания HANDLE hObject, // хендл объекта WAITORTIMERCALLBACK Callback, // функция обратного вызова PVOID Context // произвольный параметр ULONG dwMilliseconds, // таймаут ULONG dwFlags // флаги); |
Первый параметр – это указатель на переменную, в которую будет возвращен хендл объекта ожидания. Нужно отметить, что на самом деле это не хендл объекта и его нельзя использовать, например, с функцией CloseHandle. Этот хендл можно использовать только для передачи функции UnregisterWait или UnregisterWaitEx (о них поговорим попозже). В качестве второго параметра нужно передать хендл объекта, перехода которого в сигнальное состояние ожидает эта функция. Третий параметр – адрес функции WaitOrTimerCallback, которую мы описывали раньше. Четвертый параметр – это любое значение, которое просто передается функции WaitOrTimerCallback. В качестве пятого параметра можно указать количество миллисекунд, которое определяет максимальное время ожидания объекта. После его истечения функция WaitOrTimerCallback будет вызвана со вторым параметром, равным TRUE. Если объект перешел в сигнальное состояние до истечения кванта времени, второй параметр функции WaitOrTimerCallback будет равным FASLE.
В качестве флагов можно указывать все описанные ранее значения и одно новое – WT_EXECUTEINWAITTHREAD. Его можно использовать, только если вы выполняете очень короткие операции, функция WaitOrTimerCallback будет вызвана в самом ожидающем потоке. Любая задержка в пользовательской функции приведет к тому, что поток не сможет обработать переход в сигнальное состояние объекта вовремя. Замечу, что при ожидании сигнального состояния события с ручным сбросом (manual reset event) не следует вызывать функцию PulseEvent, если не указаны флаги WT_EXECUTEINWAITTHREAD или WT_EXECUTEONLYONCE, так как в этом случае ожидающий поток не сможет обработать событие перехода объекта в сигнальное состояние.
Для остановки вызова пользовательской функции можно воспользоваться следующими функциями:
BOOL UnregisterWait( // хендложидания HANDLE WaitHandle );BOOL UnregisterWaitEx( // хендложидания HANDLE WaitHandle, // хендл объекта, устанавливаемого в сигнальное состояние после удаления HANDLE CompletionEvent ); | |||
Характеристика | Значение | ||
Начальное коли-чество потоков в пуле | 1 | ||
Когда поток удаляется | Когда количество объектов равно нулю | ||
Способ ожидания, используемый потоком | WaitForMultipleObjectsEx | ||
Поток просыпается при | Переходе объекта ядра в сигнальное состояние |
Таблица 8. Описание работы функции RegisterWaitForSingleObject
Написание многопоточных серверных приложений – одна из самых сложных областей программирования. Как правило, для этого необходимо детально знать механизмы ОС, подсистему безопасности и функционирование служб (в большинстве случаев серверные приложения являются службами). В дальнейшем я, возможно, постараюсь осветить другие аспекты данной области. Надеюсь, этот материал был вам интересен.
Список литературы
Программирование серверных приложений для Windows 2000, Дж. Рихтер, Дж. Кларк.
Недокументированные возможности Windows NT, Коберниченко А.В.
NativeAPI, Гарри Неббет
Внутреннее устройство Windows 2000, Д. Соломон, М. Руссинович.
Недокументированные возможности Windows 2000. С. Шрайбер.