7. Вывод информации в окно. Механизм перерисовки окна. Понятие области обновления окна. Операции с областью обновления окна



страница15/21
Дата28.11.2016
Размер2.21 Mb.
Просмотров5898
Скачиваний1
1   ...   11   12   13   14   15   16   17   18   ...   21

26. Синхронизация потоков в пределах одного процесса ОС Windows. Ожидаемое условие (монитор Хора). Операции с ожидаемым условием. Пример использования ожидаемого условия для синхронизации потоков.

Ожидаемое условие – механизм синхронизации, позволяющий потокам дождаться выполнения некоторого (сложного) условия. Состоит из критической секции и переменной условия.



Условные переменные обеспечивают присущую Windows реализацию синхронизации набора потоков, ожидающих конкретного результата проверки условия. Хотя эта операция была возможна и с применением других методов синхронизации в пользовательском режиме, атомарного механизма для проверки результата проверки условия и для начала ожидания изменения этого результата не существовало. Это потребовало использования в отношении таких фрагментов кода этой дополнительной синхронизации. Поток пользовательского режима инициализирует условную переменную путем вызова функции InitializeConditionVariable для установки ее исходного состояния. Когда ему нужно инициировать ожидание, связанное с этой переменной, он вызывает функцию SleepConditionVariableCS, которая использует критический раздел (который поток должен инициализировать) для ожидания изменений переменной. После изменения переменной устанавливающий поток должен использовать функцию WakeConditionVariable (или функцию WakeAllConditionVariable). В результате этого вызова освобождается критический раздел либо одного, либо всех ожидающих потоков, в зависимости от того, которая из этих функция была использована. До условных переменных для отправки сигнала об изменении переменной, например состояния рабочей очереди, часто использовалось либо уведомительное событие, либо синхронизирующее событие (в Windows API они называются авто матическим перезапуском — auto-reset, или ручным перезапуском — manual-reset). Ожидание изменения требует получения, а затем освобождения критического раздела, сопровождаемого ожиданием события. После ожидания критический раздел должен быть получен повторно. Во время этих серий получений и освобождений у потока может быть переключен контекст, вызывая проблемы, если один из потоков называется PulseEvent. При использовании условных переменных получение критического раздела может быть поддержано приложением во время вызова функции SleepConditionVariableCS, и он может быть освобожден только после выполнения работы. Это делает код записи рабочей очереди (и подобных ей реализаций) более простым и предсказуемым. Внутри системы условные переменные могут рассматриваться как порт существующих алгоритмов пуш-блокировок, имеющихся в режиме ядра, с дополнительным усложнением в виде получения и освобождения критических разделов в API-функции SleepConditionVariableCS. Условные переменные по размеру равны указателям (точно так же, как и пуш-блокировки), избегают использования диспетчера, автоматически оптимизируют во время операций ожидания список ожиданий и защищают от сопровождений блокировки. Кроме того, условные переменные полностью используют события с ключом, а не обычный объект события, который бы использовался разработчиками по своему усмотрению, что еще больше оптимизирует код даже в случаях возникновения конкуренции.

  • void InitializeConditionVariable(CONDITION_VARIABLE* CondVariable);

  • bool SleepConditionVariableCS(CONDITION_VARIABLE* CondVariable, CRITICAL_SECTION* CriticalSection, DWORD dwMilliseconds);

  • bool SleepConditionVariableSRW(CONDITION_VARIABLE* CondVariable, SRWLOCK* SRWLock, DWORD dwMilliseconds, ULONG Flags);

  • void WakeConditionVariable(CONDITION_VARIABLE* CondVariable);

  • void WakeAllConditionVariable(CONDITION_VARIABLE* CondVariable);

Пример использования ожидаемого условия:

// CRITICAL_SECTION сriticalSection;

// CONDITION_VARIABLE conditionVariable;

EnterCriticalSection(&сriticalSection);

try{


while (DataDoesntSatisfyCondition()) // функция программиста

SleepConditionVariableCS(&conditionVariable, &criticalSection, INFINITE);

}

catch (...){



LeaveCriticalSection(&сriticalSection);

throw;


}

LeaveCriticalSection(&сriticalSection);

Пример использования ожидаемого условия:

// CRITICAL_SECTION сriticalSection;

// CONDITION_VARIABLE conditionVariable;



EnterCriticalSection(&сriticalSection);

try {


ChangeData(); // процедура программиста

WakeAllConditionVariableCS(&conditionVariable);

}

catch (...){



LeaveCriticalSection(&сriticalSection);

throw;


}

LeaveCriticalSection(&сriticalSection);

События — самая примитивная разновидность объектов ядра. Они содержат счетчик числа пользователей (как и все объекты ядра) и две булевы переменные: одна сообщает тип данного объекта-события, другая — его состояние (свободен или занят).

События просто уведомляют об окончании какой-либо операции. Объекты-события бывают двух типов: со сбросом вручную (manual-reset events) и с автосбросом (auto-reset events). Первые позволяют возобновлять выполнение сразу нескольких ждущих потоков, вторые — только одного.

Объекты-события обычно используют в том случае, когда какой-то поток выполняет инициализацию, а затем сигнализирует другому потоку, что тот может продолжить работу. Инициализирующий поток переводит объект «событие» в занятое состояние и приступает к своим операциям. Закончив, он сбрасывает событие в свободное

состояние. Тогда другой поток, который ждал перехода события в свободное состояние, пробуждается и вновь становится планируемым.

Пример:


Выполнение некоторым поток действий в контексте другого потока.

HANDLE CreateEvent(SECURITY_ATTRIBUTES* lpSecurityAttributes,

bool bManualReset, bool bInitialState, LPCTSTR lpName);

Параметр fManualReset (булева переменная) сообщает системе, хотите Вы создать событие со сбросом вручную (TRUE) или с автосбросом (FALSE). Параметр fInitialState определяет начальное состояние события — свободное (TRUE) или занятое (FALSE).

После того как система создает объект-событие, CreateEvent возвращает описатель

события, специфичный для конкретного процесса. Потоки из других процессов могут получить доступ к этому объекту:

1) вызовом CreateEvent с тем же параметром lpName;

2) наследованием описателя;

3) применением функции DuplicateHandle;

4) вызовом . HANDLE OpenEvent(DWORD fdwAccess, BOOL fInherit, PCTSTR pszName); с передачей в параметре pszName имени, совпадающего с указанным в аналогичном параметре функции CreateEvent.

Ненужный объект ядра «событие» следует, как всегда, закрыть вызовом bool CloseHandle(HANDLE hObject);.

Создав событие, Вы можете напрямую управлять его состоянием. Чтобы перевести его в свободное состояние, Вы вызываете:



BOOL SetEvent(HANDLE hEvent);

А чтобы поменять его на занятое:



BOOL ResetEvent(HANDLE hEvent);

Для событий с автосбросом действует следующее правило. Когда его ожидание потоком успешно завершается, этот объект автоматически сбрасывается в занятое состояние. Отсюда и произошло название таких объектов-событий. Для этого объекта обычно не требуется вызывать ResetEvent, поскольку система сама восстанавливает его состояние. А для событий со сбросом вручную никаких побочных эффектов успешного ожидания не предусмотрено.

Закончив свою работу с данными, поток вызывает SetEvent, которая разрешает

системе возобновить выполнение следующего из двух ждущих потоков. И опять мы

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

он тоже вызовет SetEvent, после чего с блоком памяти сможет монопольно оперировать третий, последний поток. Обратите внимание, что использование события с автосбросом снимает проблему с доступом вторичных потоков к памяти как для чтения, так и для записи; Вам больше не нужно ограничивать их доступ только чтением.

bool PulseEvent(HANDLE hEvent); –освобождает событие и тут же переводит его обратно в занятое состояние; ее вызов равнозначен последовательному вызову SetEvent и ResetEvent. Если Вы вызываете PulseEvent для события со сбросом вручную, любые потоки, ждущие этот объект, становятся планируемыми. При вызове этой функции применительно к событию с автосбросом пробуждается только один из ждущих потоков. А если ни один из потоков не ждет объект-событие, вызов функции не дает никакого эффекта. Абсолютно неясно, какой из потоков заметит этот импульс и станет планируемым.


27. Синхронизация потоков разных процессов с помощью объектов ядра. Понятие свободного и занятого состояния объекта ядра. Процедуры ожидания освобождения объекта ядра. Перевод объекта ядра в свободное состояние. Объекты синхронизации: блокировки, семафоры, события.

Почти все объекты ядра годятся и для решения задач синхронизации. В случае синхронизации потоков о каждом из этих объектов говорят, что он находится либо в свободном (signaled state), либо в занятом состоянии (nonsignaled state). Переход из одного состояния в другое осуществляется по правилам, определенным Microsoft для каждого из объектов ядра. Так, объекты ядра «процесс» сразу после создания всегда находятся в занятом состоянии. В момент завершения процесса операционная система автоматически освобождает его объект ядра «процесс», и он навсегда остается в этом состоянии.

Объект ядра «процесс» пребывает в занятом состоянии, пока выполняется сопоставленный с ним процесс, и переходит в свободное состояние, когда процесс завершается. Внутри этого объекта поддерживается булева переменная, которая при создании объекта инициализируется как FALSE («занято»). По окончании работы процесса операционная система меняет значение этой переменной на TRUE, сообщая тем самым, что объект свободен.

Если Вы пишете код, проверяющий, выполняется ли процесс в данный момент, Вам нужно лишь вызвать функцию, которая просит операционную систему проверить значение булевой переменной, принадлежащей объекту ядра «процесс». Тут нет ничего сложного. Вы можете также сообщить системе, чтобы та перевела Ваш поток в состояние ожидания и автоматически пробудила его при изменении значения булевой переменной с FALSE на TRUE. Тогда появляется возможность заставить поток в родительском процессе, ожидающий завершения дочернего процесса, просто заснуть до освобождения объекта ядра, идентифицирующего дочерний процесс. В дальнейшем Вы увидите, что в Windows есть ряд функций, позволяющих легко решать эту задачу. Я только что описал правила, определенные Microsoft для объекта ядра «процесс». Точно такие же правила распространяются и на объекты ядра «поток». Они тоже сразу после создания находятся в занятом состоянии. Когда поток завершается, операционная система автоматически переводит объект ядра «поток» в свободное состояние. Таким образом, используя те же приемы, Вы можете определить, выполняется ли в данный момент тот или иной поток. Как и объект ядра «процесс», объект ядра «поток» никогда не возвращается в занятое состояние.

Следующие объекты ядра бывают в свободном или занятом состоянии:

· процессы

· уведомления об изменении файлов

· потоки


· события

· задания

· ожидаемые таймеры

· файлы


· семафоры

· консольный ввод

· мьютексы

Потоки могут засыпать и в таком состоянии ждать освобождения какого-либо объекта. Правила, по которым объект переходит в свободное или занятое состояние, зависят от типа этого объекта.

Wait функции позволяют потоку в любой момент приостановиться и ждать освобождения какого-либо объекта ядра. Из всего семейства этих функций чаще всего используется WaitForSingleObject:

DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);

Когда поток вызывает эту функцию, первый параметр, hObject, идентифицирует объект ядра, поддерживающий состояния «свободен занят». (То есть любой объект, упомянутый в списке из предыдущего раздела.) Второй параметр, dwMilliseconds, указывает, сколько времени (в миллисекундах) поток готов ждать освобождения объекта. Следующий вызов сообщает системе, что поток будет ждать до тех пор, пока не завершится процесс, идентифицируемый описателем hProcess:

WaitForSingleObject(hProcess, INFINITE);

В данном случае константа INFINITE, передаваемая во втором параметре, подсказывает системе, что вызывающий поток готов ждать этого события хоть целую вечность. Именно эта константа обычно и передается функции WaitForSingleObject, но Вы можете указать любое значение в миллисекундах. Кстати, константа INFINITE определена как 0xFFFFFFFF (или –1). Разумеется, передача INFINITE не всегда безопасна. Если объект так и не перейдет в свободное состояние, вызывающий поток никогда не проснется; одно утешение: тратить драгоценное процессорное время он при этом не будет.

Вот пример, иллюстрирующий, как вызывать WaitForSingleObject со значением таймаута, отличным от INFINITE:

DWORD dw = WaitForSingleObject(hProcess, 5000);

switch (dw) {

case WAIT_OBJECT_0:

// процесс завершается

break;

case WAIT_TIMEOUT:



// процесс не завершился в течение 5000 мс

break;


case WAIT_FAILED:

// неправильный вызов функции (неверный описатель?)

break;

}

Данный код сообщает системе, что вызывающий поток не должен получать процессорное время, пока не завершится указанный процесс или не пройдет 5000 мс (в зависимости от того, что случится раньше). Поэтому функция вернет управление либо до истечения 5000 мс, если процесс завершится, либо примерно через 5000 мс, если процесс к тому времени не закончит свою работу. Заметьте, что в параметре dwMilliseconds можно передать 0, и тогда WaitForSingleObject немедленно вернет управление. Возвращаемое значение функции WaitForSingleObject указывает, почему вызывающий поток снова стал планируемым. Если функция возвращает WAIT_OBJECT_0, объект свободен, а если WAIT_TIMEOUT — заданное время ожидания (таймаут) истекло. При передаче неверного параметра (например, недопустимого описателя) WaitForSingleObject возвращает WAIT_FAILED. Чтобы выяснить конкретную причину ошибки, вызовите функцию GetLastError. Функция WaitForMultipleObjects аналогична WaitForSingleObject с тем исключением, что позволяет ждать освобождения сразу нескольких объектов или какого то одного из списка объектов:



DWORD WaitForMultipleObjects(

DWORD dwCount,

CONST HANDLE* phObjects,

BOOL fWaitAll,

DWORD dwMilliseconds);

Параметр dwCount определяет количество интересующих Вас объектов ядра. Его значение должно быть в пределах от 1 до MAXIMUM_WAIT_OBJECTS (в заголовочных файлах Windows оно определено как 64). Параметр phObjects — это указатель на массив описателей объектов ядра. WaitForMultipleObjects приостанавливает поток и заставляет его ждать освобождения либо всех заданных объектов ядра, либо одного из них. Параметр fWaitAll как раз и определяет, чего именно Вы хотите от функции. Если он равен TRUE, функция не даст потоку возобновить свою работу, пока не освободятся все объекты. Параметр dwMilliseconds идентичен одноименному параметру функции WaitForSingleObject. Если Вы указываете конкретное время ожидания, то по его истечении функция в любом случае возвращает управление. И опять же, в этом параметре обычно передают INFINITE (будьте внимательны при написании кода, чтобы не создать ситуацию взаимной блокировки). Возвращаемое значение функции WaitForMultipleObjects сообщает, почему возобновилось выполнение вызвавшего ее потока. Значения WAIT_FAILED и WAIT_TIMEOUT никаких пояснений не требуют. Если Вы передали TRUE в параметре fWaitAll и все объекты перешли в свободное состояние, функция возвращает значение WAIT_OBJECT_0. Если же fWaitAll приравнен FALSE, она возвращает управление, как только освобождается любой из объектов. Вы, по видимому, захотите выяснить, какой именно объект освободился. В этом случае возвращается значение от WAIT_OBJECT_0 до WAIT_OBJECT_0 + dwCount – 1. Иначе говоря, если возвращаемое значение не равно WAIT_TIMEOUT или WAIT_FAILED, вычтите из него значение WAIT_OBJECT_0, и Вы получите индекс в массиве описателей, на который указывает второй параметр функции WaitForMultipleObjects. Индекс подскажет Вам, какой объект перешел в незанятое состояние. Поясню сказанное на примере.

HANDLE h[3];

h[0] = hProcess1;

h[1] = hProcess2;

h[2] = hProcess3;

DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);

switch (dw) {

case WAIT_FAILED:

// неправильный вызов функции (неверный описатель?)

break;

case WAIT_TIMEOUT:



// ни один из объектов не освободился в течение 5000 мс

break;


case WAIT_OBJECT_0 + 0:

// завершился процесс, идентифицируемый h[0], т. е. описателем (hProcess1)

break;

case WAIT_OBJECT_0 + 1:



// завершился процесс, идентифицируемый h[1], т. е. описателем (hProcess2)

break;


case WAIT_OBJECT_0 + 2:

// завершился процесс, идентифицируемый h[2], т. е. описателем (hProcess3)

break;

}

Если Вы передаете FALSE в параметре fWaitAll, функция WaitForMultipleObjects сканирует массив описателей (начиная с нулевого элемента), и первый же освободившийся объект прерывает ожидание. Это может привести к нежелательным последствиям. Например, Ваш поток ждет завершения трех дочерних процессов; при этом Вы передали функции массив с их описателями. Если завершается процесс, описатель которого находится в нулевом элементе массива, WaitForMultipleObjects возвращает управление. Теперь поток может сделать то, что ему нужно, и вновь вызвать эту функцию, ожидая завершения другого процесса. Если поток передаст те же три описателя, функция немедленно вернет управление, и Вы снова получите значение WAIT_OBJECT_0. Таким образом, пока Вы не удалите описатели тех объектов, об освобождении которых функция уже сообщила Вам, код будет работать некорректно.



Перевод в свободное(сигнальное) состояние.

Тип объекта

Устанавливается в сигнальное состояние, когда

Влияние на ожидающие потоки

Процесс

Завершается последний поток

Освобождаются все потоки

Поток

Завершается поток

Освобождаются все потоки

Событие(уведомительного типа)

Поток устанавливает событие

Освобождаются все потоки

Событие(синхронизирующего типа)

Поток устанавливает событие

Один поток освобождается и может получить повышение приоритета; объект события сбрасывается

Шлюз(блокирующего типа)

Поток сообщает

о шлюзе


Освобождается и получает более высокий приоритет первый ожидающий поток

Шлюз (сигнализирующего типа)

Поток сообщает о типе

Освобождается первый ожидающий поток

Событие, снабженное ключом

Поток устанавливает событие с помощью ключа

Освобождается поток, ожидающий ключа и относящийся к тому же процессу, что и сигнализирующий поток

Семафор

Счетчик семафора уменьшается на 1

Освобождается один поток

Таймер (уведомительного типа)

Настало установленное время, или истек интервал времени

Освобождаются все потоки

Таймер (синхронизирующего типа)

Настало установленное время, или истек интервал времени

Освобождается один поток

Мьютекс

Поток освобождает мьютекс

Освобождается один поток, который становится владельцем мьютекса

Очередь

Элемент помещается в очередь

Освобождается один поток

Блокировка – mutex (mutually exclusive), бинарный семафор. Используется для обеспечения монопольного доступа к некоторому ресурсу со стороны нескольких потоков (различных процессов).



  • HANDLE CreateMutex(SECURITY_ATTRIBUTES* lpMutexAttributes, bool bInitialOwner, LPCTSTR lpName);

  • HANDLE OpenMutex(DWORD dwDesiredAccess, bool bInheritHandle, LPCTSTR lpName);

  • DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

  • bool ReleaseMutex(HANDLE hMutex);

  • bool CloseHandle(HANDLE hObject);

Семафор – объект ядра, использующийся для учета ресурсов. Семафор имеет внутри счетчик. Этот счетчик снизу ограничен значением 0 (семафор занят) и некоторым верхним значением N. В диапазоне 1..N семафор является свободным. Семафоры можно считать обобщением блокировки на несколько ресурсов.

  • HANDLE CreateSemaphore(SECURITY_ATTRIBUTES* lpSecurityAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName);

  • HANDLE OpenSemaphore(DWORD dwDesiredAccess, bool bInheritHandle, LPCTSTR lpName);

  • DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

  • bool ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LONG* lpPreviousCount);

  • bool CloseHandle(HANDLE hObject);

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

Событие со сбросом вручную – manual-reset event;

Событие с автосбросом – auto-reset event.

Пример:


Выполнение некоторым поток действий в контексте другого потока.

Функции:


  • HANDLE CreateEvent(SECURITY_ATTRIBUTES* lpSecurityAttributes,

  • bool bManualReset, bool bInitialState, LPCTSTR lpName); OpenEvent.

  • bool SetEvent(HANDLE hEvent); bool ResetEvent(HANDLE hEvent);

  • bool PulseEvent(HANDLE hEvent); – если это событие со сбросом вручную, то запускаются все ожидающие потоки; если это событие с автосбросом, то запускается лишь один из ожидающих потоков.

  • bool CloseHandle(HANDLE hObject);


28. Синхронизация потоков разных процессов с помощью объектов ядра. Понятие свободного и занятого состояния объекта ядра. Процедуры ожидания освобождения объекта ядра. Ожидаемые таймеры. Оконные таймеры.

Первую часть вопроса читаем в вопросе 27.

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

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

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

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

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

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

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

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

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

через 5 секунд после вызова SetWaitableTimer:

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

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

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

BOOL CancelWaitableTimer(HANDLE hTimer);

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



Поделитесь с Вашими друзьями:
1   ...   11   12   13   14   15   16   17   18   ...   21


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал