Процессы в Windows - События

ОГЛАВЛЕНИЕ


События

События (Event), также как и мьютексы имеют два состояния - установленное и сброшенное. События бывают со сбросом вручную и с автосбросом. Когда поток дождался (wait-функция вернула управление) события с автосбросом, такое событие автоматически сбрасывается. В противном случае событие нужно сбрасывать вручную, вызвав функцию ResetEvent(). Допустим, сразу несколько потоков ожидают одного и того же события, и событие сработало. Если это было событие с автосбросом, то оно позволит работать только одному потоку (ведь сразу же после возврата из его wait-функции событие сбросится автоматически!), а остальные потоки останутся ждать. Если же это было событие со сбросом вручную, то все потоки получат управление, а событие так и останется в установленном состоянии, пока какой-нибудь поток не вызовет ResetEvent().

Пример 5. Вот еще один пример многопоточного приложения. Программа имеет два потока; один готовит данные, а второй отсылает их на сервер. Разумно распараллелить их работу. Здесь потоки должны работать по очереди. Сначала первый поток готовит порцию данных. Потом второй поток отправляет ее, а первый тем временем готовит следующую порцию и т.д. Для такой синхронизации понадобится два event'а с автосбросом.

unsigned __stdcall CaptureThreadFunc( void * arg) // Поток, готовящий данные
{
while (bSomeCondition)
{
WaitForSingleObject(m_hEventForCaptureTh,INFINITE); // Ждем своего события
... // Готовим данные
SetEvent(hEventForTransmitTh); // Разрешаем работать второму потоку
}
_endthreadex( 0 );
return 0;
};

unsigned __stdcall TransmitThreadFunc( void * arg) // Поток, отсылающий данные.
{
while (bSomeCondition)
{
WaitForSingleObject(m_hEventForTransmitTh,INFINITE); // Ждем своего события
... // Данные готовы, формируем из них пакет для отправки
SetEvent(hEventForCaptureTh); // Разрешаем работать первому потоку, а сами...
... // отправляем пакет
}
_endthreadex( 0 );
return 0;
};

int main(int argc, char* argv[]) // Основной поток
{
// Создаем два события с автосбросом, со сброшенным начальным состоянием
hEventForCaptureTh = CreateEvent(NULL,FALSE,FALSE,NULL);
hEventForTransmitTh = CreateEvent(NULL,FALSE,FALSE,NULL);

// Создаем потоки
hCaptureTh = (HANDLE)_beginthreadex( NULL, 0, &CaptureThreadFunc, 0, 0,&uTh1);
hTransmitTh = (HANDLE)_beginthreadex( NULL, 0, &TransmitThreadFunc, 0, 0,&uTh2);
// Запускаем первый поток
SetEvent(hEventForCaptureTh);

....
}

Пример 6. Другой пример. Программа непрерывно в цикле производит какие-то вычисления. Нужно иметь возможность приостановить на время ее работу. Допустим, это просмотрщик видео файлов, который в цикле, кадр за кадром отображает информацию на экран. Не будем вдаваться в подробности видео функций. Реализуем функции Pause и Play для программы. Используем событие со сбросом вручную.

// Главная функция потока, которая в цикле отображает кадры
unsigned __stdcall VideoThreadFunc( void * arg)
{
while (bSomeCondition)
{
WaitForSingleObject(m_hPauseEvent,INFINITE); // Если событие сброшено, ждем
... // Отображаем очередной кадр на экран
}
_endthreadex( 0 );
return 0;
};

void Play()
{
SetEvent(m_hPauseEvent);
};

void Pause()
{
ResetEvent(m_hPauseEvent);
};

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

Пример 7. Реализуем функцию NextFrame() для предыдущего примера для промотки файла вручную по кадрам.

void NextFrame()
{
PulseEvent(m_hPauseEvent);
};