Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

Легко ли ждать

Начнем с простого: расскажем о том, как задача может дождаться завершения другой задачи либо завершения процесса.

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

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

Ожидание завершения задачи или процесса

Если нужно дождаться завершения одной задачи или одного процесса, лучше всего воспользоваться для этого функцией WaitForSingleObject XE "WaitForSingleObject" , с которой вы уже знакомы из предыдущих приложений.

Прототип функции WaitForSingleObject представлен ниже:


DWORD WaitForSingleObject(
  HANDLE hObject,    // идентификатор объекта 
  DWORD  dwTimeout); // время ожидания в миллисекундах

В качестве параметра hObject этой функции нужно передать идентификатор объекта, для которого выполняется ожидание, а в качестве параметра dwTimeout - время ожидания в миллисекундах (ожидание может быть и бесконечным, если для времени указать значение INFINITE).

Многие объекты операционной системы Microsoft Windows NT, такие, например, как идентификаторы задач, процессов, файлов, могут находиться в двух состояниях - отмеченном (signaled) и неотмеченном (nonsignaled). В частности, если задача или процесс находятся в состоянии выполнения (то есть работают), соответствующие идентификаторы находятся в неотмеченном состоянии. Когда же задача или процесс завершают свою работу, их идентификаторы отмечаются (то есть переходят в отмеченное состояние).

Если задача создает другую задачу или процесс, и затем вызывает функцию WaitForSingleObject XE "WaitForSingleObject" , указав ей в качестве первого параметра идентификатор созданной задачи, а в качестве второго - значение INFINITE XE "INFINITE" , родительская задача переходит в состояние ожидания. Она будет находиться в состоянии ожидания до тех пор, пока дочерняя задача или процесс не завершит свою работу.

Заметим, что функция WaitForSingleObject не проверяет состояние идентификатора дочерней задачи или процесса в цикле, дожидаясь ее (или его) завершения. Такое действие привело бы к тому, что родительской задаче выделялись бы кванты процессорного времени, а это как раз то, чего мы хотели бы избежать. Вместо этого функция WaitForSingleObject сообщает планировщику задач, что выполнение родительской задачи, вызвавшей эту функцию, необходимо приостановить до тех пор, пока дочерняя задача или процесс не завершит свою работу.

Принимая решение о выделении родительской задаче кванта процессорного времени, планировщик проверяет состояние дочерней задачи. Квант времени выделяется планировщиком родительской задаче только в том случае, если дочерняя задача завершила свою работу и ее идентификатор находится в отмеченном состоянии. Такая проверка не отнимает много времени и, следовательно, не приводит к заметному снижению производительности системы.

На рис. 4.1 показано, как задача с номером 1 ожидает завершение задачи с номером 2, имеющей идентификатор hThread2.

Рис. 4.1. Ожидание завершения задачи

Пунктирной стрелкой здесь показано событие, которое ведет к завершению ожидания. Этим событием, очевидно, является завершение работы задачи hThread2.

В качестве примера использования функции WaitForSingleObject XE "WaitForSingleObject" для ожидания завершения дочернего процесса, рассмотрим немного измененный фрагмент исходного текста приложения PSTART, описанного в предыдущей главе:


if(CreateProcess(NULL, ofn.lpstrFile, NULL, NULL,
        FALSE, dwCreationFlags, NULL, NULL, &si, &pi))
{
  . . .
  if(WaitForSingleObject(pi.hProcess, INFINITE) != 
    WAIT_FAILED)
  {
    GetExitCodeProcess(pi.hProcess, &dwExitCode);
    . . .
  }
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
}

Здесь главная задача с помощью функции CreateProcess запускает процесс. Идентификатор этого процесса сохраняется в поле hProcess структуры pi. Если запуск процесса произошел успешно, главная задача приложения приостанавливает свою работу до тех пор, пока запущенный процесс не завершит свою работу. Для этого она вызывает функцию WaitForSingleObject XE "WaitForSingleObject" , передавая ей идентификатор запущенного процесса.

Если родительский процесс не интересуется судьбой своего дочернего процесса, функция WaitForSingleObject не нужна. В этом случае главная задача может сразу закрыть идентификаторы дочернего процесса и главной задачи дочернего процесса, оборвав “родительские узы”:


if(CreateProcess(NULL, ofn.lpstrFile, NULL, NULL,
        FALSE, dwCreationFlags, NULL, NULL, &si, &pi))
{
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
}

Запущенный таким образом процесс называется отсоединенным (detached). Он будет жить своей жизнью независимо от состояния запустившего его процесса.

Теперь поговорим о коде завершения функции WaitForSingleObject.

В случае ошибки функция возвращает значение WAIT_FAILED. При этом код ошибки можно получить при помощи функции GetLastError.

Если же функция завершилась успешно, она может вернуть одно из следующих трех значений: WAIT_OBJECT_0, WAIT_TIMEOUT или WAIT_ABANDONED.

Если состояние идентификатора объекта, для которого выполнялось ожидание, стало отмеченным, функция фозвращает значение WAIT_OBJECT_0. Таким образом, когда мы ожидаем завершение задачи и задача завершилась “естественным образом”, функция WaitForSingleObject XE "WaitForSingleObject" вернет именно это значение.

Если время ожидания, заданное во втором параметре функции WaitForSingleObject истекло, но объект так и не перешел в отмеченное состояние, возвращается значение WAIT_TIMEOUT. Очевидно, при бесконечном ожидании вы никогда не получите этот код завершения.

Код завершения WAIT_ABANDONED возвращается для объекта синхронизации типа Mutex (его мы рассмотрим позже), который не был освобожден задачей, завершившей свою работу. Таким образом, в этом случае ожидание было отменено и соответствующий объект (Mutex) не перешел в отмеченное состояние.

Ожидание завершения нескольких задач или процессов

Часто одна задача должна дожидаться завершения сразу нескольких задач или процессов, либо одной из нескольких задач или процессов. Такое ожидание нетрудно выполнить с помощью функции WaitForMultipleObjects, прототип которой приведен ниже:


DWORD WaitForMultipleObjects(
  DWORD cObjects,     // количество идентификаторов в массиве 
  CONST HANDLE *lphObjects, // адрес массива идентификаторов 
  BOOL  fWaitAll,           // тип ожидания 
  DWORD dwTimeout);         // время ожидания в миллисекундах 

Через параметр lphObjects функции WaitForMultipleObjects нужно передать адрес массива идентификаторов. Размер этого массива передается через параметр cObjects.

Если содержимое параметра fWaitAll равно TRUE, задача переводится в состояние ожидания до тех пор, пока все задачи или процессы, идентификаторы которых хранятся в массиве lphObjects, не завершат свою работу. В том случае, когда значение параметра fWaitAll равно FALSE, ожидание прекращается, когда одна из указанных задач или процессов завершит свою работу. Для выполнения бесконечного ожидания, как и в случае функции WaitForSingleObject XE "WaitForSingleObject" , через параметр dwTimeout следует передать значение INFINITE.

Как пользоваться этой функцией?

Пример вы можете найти в исходных текстах приложеения MultiSDI, описанного ранее.

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


HANDLE hThreads[3];

После этого в массив следует записать идентификаторы запущенных задач или процессов:


hThreads[0] = (HANDLE)_beginthread(PaintEllipse, 0, 
  (void*)hWnd);
hThreads[1] = (HANDLE)_beginthread(PaintRect,    0, 
  (void*)hWnd);
hThreads[2] = (HANDLE)_beginthread(PaintText,    0, 
  (void*)hWnd);

Для выполнения ожидания следует вызвать функцию WaitForMultipleObjects, передав ей количество элементов в массиве идентификаторов, адрес этого массива, тип и время ожидания:


WaitForMultipleObjects(3, hThreads, TRUE, INFINITE);

В данном случае задача, вызвавшая функцию WaitForMultipleObjects, перейдет в состояние ожидания до тех пор, пока все три задачи не завершат свою работу.

Функция WaitForMultipleObjects может вернуть одно из следующих значений:

·       WAIT_FAILED (при ошибке);

·       WAIT_TIMEOUT (если время ожидания истекло);

·       значение в диапазоне от WAIT_OBJECT_0 до (WAIT_OBJECT_0 + cObjects - 1), которое, в зависимости от содержимого параметра fWaitAll, либо означает что все ожидаемые идентификаторы перешли в отмеченное состояние (если fWaitAll был равен TRUE), либо это значение, если из него вычесть константу WAIT_OBJECT_0, равно индексу идентификатора отмеченного объекта в массиве идентфикаторов lphObjects.

·       значение в диапазоне от WAIT_ABANDONED_0 до (WAIT_ABANDONED_0 + cObjects - 1), если ожидание для объектов Mutex было отменено. Индекс соответствующего объекта в массиве lphObjects можно определить, если вычесть из кода возврата значение WAIT_ABANDONED_0.

[Назад] [Содеожание] [Дальше]