Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Desktop / For_exams / Smirnov / 1 - копия (32)

.txt
Скачиваний:
34
Добавлен:
27.03.2015
Размер:
11.02 Кб
Скачать
33. Ожидаемые таймеры
Ожидающие таймеры - объекты ядра, которые предназначены для отсчета промежутков времени. Окончание временного интервала определяется по переходу таймера в свободное состояние (signaled). Момент перехода таймера в свободное состояние определяется одной из ожидающих функций. По аналогии с радиоэлектроникой, ожидающий таймер можно представить как мультивибратор, работающий в одном из двух режимов, ожидающим запуска от внешнего сигнала или автогенераторном, когда формируется непрерывная последовательность импульсов. Основное применение ожидающих таймеров - синхронизация потоков в многопоточном программировании.
Наиболее частая область применения ожидающих таймеров - периодическое выполнение определенной задачи. Например, требуется с определенным периодом собирать данные и обрабатывать их. Ни источник данных, ни их обработка не имеют значения, главное в задаче - периодичность сбора.
Для создания и управления ожидающими таймерами существует несколько функций:
• HANDLE CreateWaitableTimer(LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset, LPCTSTR lpTimerName ) - функция создает ожидающий таймер в занятом состоянии, из которого он выводится принудительно, т.е. после создания объект не активен. Таймер может быть двух типов с автосбросом или ручным сбросом, определяется параметром bManualReset. Если таймер с ручным сбросом, то при переходе в свободное состояние запускаются все потоки, которые его ожидали. Таймер с автосбросом запускает только один поток. Первый параметр обычно всегда NULL, последний - имя таймера. Имя используется для совместного разделения объекта между процессами.
• BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER *pDueTime,LONG lPeriod,PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID pvArgToCompletionRoutine, BOOL bResume) - функция запускает таймер и определяет все его параметры. Функция может быть вызвана в любой момент для перенастройки таймера, если таймер в этот момент был в занятом состоянии, то вызов функции не освобождает его, после вызова функции таймер продолжит работу с новыми параметрами. После вызова функции таймер переходит в занятое состояние и отрабатывает время, указанное во втором параметре.
- Первый параметр - дескриптор таймера.
- Второй параметр pDueTime определяет время перехода таймера в свободное состояние (signaled) или время срабатывания таймера. В течении этого времени поток, ожидающий срабатывания таймера, находится в спящем состоянии. Если указана функция асинхронного вызова (APC), то она ставится в момент срабатывания таймера в очередь на выполнение. Параметр pDueTime может быть определен как абсолютное или относительное время, абсолютное время определяет момент первого запуска и является величиной положительной. Определение абсолютного времени требует подготовительных операций по преобразованию полной даты в величину типа LARGE_INTEGER. Относительное время определяет момент первого срабатывания таймера после вызова функции. Относительное время является величиной отрицательной, выраженной в 100нс интервалах.
- Третий параметр lPeriod определяет режим работы и одновременно период повторения срабатываний ожидающего таймера. Если период равен нулю -таймер сработает однократно, если не нуль, срабатывания будут повторятся с указанным периодом. Величина указывается в миллисекундах. Следует заметить, что установка lPeriod с точностью до 1 мсек не означает, что потоку точно в это время будут предоставлены ресурсы процессора. В действительности период пульсирующего таймера приблизительно кратен периоду времени, предоставлямого потоку системой. Например, на моем компьютере время отводимое потоку системой составляет 15,625 мсек. Если установить период срабатывания таймера равным 20 мсек, то в действительности он будет срабатывать с периодом приблизительно 32 мсек.
- Четвертый параметр pfnCompletionRoutine определяет указатель на необязательную функцию асинхронного вызова (APC), которая помещается в очередь функций APC в момент срабатывания таймера и затем вызывается. Ее вызов производится из того потока, который вызвал функцию SetWaitableTimer, но при условии, что он находится в состоянии ожидания, т.е. поток должен вызвать ожидающую функцию и находиться в ней. Если поток не находится в ожидающей функции, то функция асинхронного вызова не будет поставлена в очередь. Так осуществляется защита от переполнения очереди APC вызовами таймера. Функция асинхронного вызова должна завершаться до следующего срабатывания таймера. Если при наличии функции APC вызывается SetWaitableTimer с новыми параметрами, то функция APC немедленно выполняется, а затем уже выполняется для новых параметров.
- Пятый параметр передает в функцию асинхронного вызова (APC) произвольный аргумент, например указатель на объект или структуру.
- Последний параметр bResume, если он не нуль, выводит машину из спящего состояния по срабатыванию таймера.
• HANDLE OpenWaitableTimer(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpTimerName) - функция открывает существующий именованный таймер. Используется для доступа к одному таймеру из разных процессов.
• BOOL CancelWaitableTimer(HANDLE hTimer) - функция останавливает таймер и отменяет выполнение функции APC, не изменяя состояния таймера. Если таймер был в занятом состоянии, то потоки остаются в спящем состоянии до возобновления работы таймера. Если же таймер оказался на этот момент в свободном состоянии, то он в нем и остается, потоки будут работать. При перенастройке таймера эту функцию вызывать не нужно.
Если поток, который установил таймер, уничтожается, то таймер останавливается без изменения состояния, но не уничтожается вместе с потоком. Если есть функция APC, она уничтожается, в противном случае уничтожение потока не оказывает влияния та таймер. Таймер с ручной установкой после перехода в свободное состояние остается в нем до сброса функцией SetWaitableTimer. Это касается и периодического таймера, таким образом периодический таймер с ручной установкой фактически не является периодическим таймером. В MSDN не поясняется каким образом можно полностью уничтожить ожидающий таймер, можно понимать, что не именованный таймер будет уничтожен после вызова CloseHandle, а именованный, когда последний поток вызовет CloseHandle, во всяком случае логически должно быть так.
Рассмотрим часто встречающуюся задачу, для решения которой необходимо использовать ожидающий таймер. Задача заключается в сборе информации от внешнего устройства, ее последующей обработки и отображении. Центральный компьтер должен задать начало цикла для подчиненного устройства и через некоторое время получить информацию от него. Для большей конкретизации и приближения к работе ожидающего таймера рассмотрим ситуацию, когда внешнее устройство производит рабочий цикл по своей программе(микропроцессор) и через известное время от него нужно получить данные. Начало цикла задает центральный компьютер, а через определенное время он же забирает данные. Линия связи управляется собственным драйвером и исключена из задачи. Организовать такой цикл можно с помощью ожидающего таймера как показано на рисунке 1.



Рис. 1

Задача будет решена если перед переводом рабочего потока в спящее состояние (точка 2,4) послать сигнал внешнему устройству на выполнение рабочего цикла, а после срабатывания таймера (точка 3) произвести загрузку данных. Загрузка данных может быть произведена в функции APC или в прямо в пробудившемся потоке.
В одной из программ мне пришлось использовать ожидающий таймер так как описано выше. Уже потом, читая книгу Рихтера "Windows via C++" , мне попались такие строки в главе, посвященной синхронизации потоков: "Как-то раз мне понадобилось, чтобы один из потоков в пуле (управляемом через порт завершения ввода-вывода) пробуждался по таймеру через определенные интервалы времени. К сожалению, такую функциональность ожидаемые таймеры не поддерживают. Для решения этой задачи мне пришлось создать отдельный поток, который всего-то и делал, что настраивал ожидаемый таймер и ждал его освобождения. Когда таймер переходил в свободное состояние, этот по ток вызывал PostQueuedCompletionStatus, передавая соответствующее уведомление потоку в пуле". К сожалению, в приложении к книге по этой теме нет примера.
Что автор имел ввиду не совсем понятно, даже прочитав оригинал книги. Может сложиться впечатление, что с помощью ожидающего таймера действительно нелья организовать периодическое пробуждение рабочего потока, но это не так. Именно эту задачу и решает ожидаемый таймер в классе, который описан далее. Собственно эта фраза и явилась причиной написания статьи.
Класс достаточно простой и обеспечивает функционирование только таймера с автосбросом, его функциональность можно легко расширить. Класс CWaitTimer и содержит всего несколько функций:
• BOOL CreateWaitTimer(double delay,long period,void* pOwner) - функция задает режим таймера, одноктатный или периодический, устанавливает времена срабатывания и передает в класс указатель на объект, содержащий таймер.
• static void CALLBACK funTimerAPC_pulse(void* pvArg,DWORD dwTimerLowValue,DWORD dwTimerHighValue) - функция APC для пульсирующего таймера.
• static void CALLBACK funTimerAPC_single(void* pvArg,DWORD dwTimerLowValue,DWORD dwTimerHighValue) - функция APC для однократного таймера.
• BOOL Wait() - функция, реализующая основную функциональность таймера, пульсирующего и однократного.
• void ReWait(double delay, long periodPulseNew) - функция перезагрузки таймера с новыми параметрами.
• double GetDelay() - функция возвращает время срабатывания таймера.
Файл waittimer.h можно скачать здесь. Если Вам потребуются дополнительные материалы, обратитесь к автору с письмом (dslev@yandex.ru).
Примечание.
Применение объектов ядра для синхронизации потоков имеет некоторые тонкости, которые следует учитывать. Особенно это касается взаимодействия с аппаратной частью и связано с прекращением работы потока. Обычно рекомендуется прекращать работу потока естественным образом при выходе из функции потока, в этом случае автоматически удаляются все части потока, в том числе и объекты, созданные в потоке. Функцию TerminateThread не рекомендуется использовать, поскольку она не всегда удаляет все объекты потока. Поэтому выход из потока, работающего в цикле, лучше осуществлять путем установки флага, который будет анализироваться потоком. Это конечно, дополнительный код, но безопасный путь. Установка флага из другого потока должна быть синхронизирована с рабочим потоком, который периодически анализирует его состояние. Для этой цели очень удобны функции Interlockxxx. Иногда при взаимодействии с аппаратной частью были замечены случаи, когда поток не завершался немедленно после анализа флага, а ожидал завершения работы объекта ядра, который взаимодействовал с драйвером устройства. В результате выход из функции потока производился дважды и оба раза завершался с невразумительными ошибками, которые требуют глубокого анализа. В таких случаях следует все-таки использоватьTerminateThread, предварительно удалив вручную не удаляемые объекты.
Соседние файлы в папке Smirnov