Шаг 292.
Модели потоков. Функции синхронизации. Функции ожидания нескольких объектов

    На этом шаге мы рассмотрим синтаксис и использование таких функций.

    Иногда требуется задержать выполнение потока до срабатывания одного или всех сразу объектов из группы. Для решения подобной задачи служат функции WaitForMultipleObjects и MsgWaitForMultipleObjects. Рассмотрим их более подробно.

type
  TWOHandleArray = array[0..MAXIMUM_WAIT_OBJECTS - 1] of THandle; 
  PWOHandleArray = ^TWOHandleArray;

function WaitForMultipieObjects(
  nCount: DWORD;              // Количество объектов
  lpHandles: PWOHandleArray;  // Адрес массива объектов
  bWaitAll: BOOL;             // Необходимость ожидания всех
                              // или любого из объектов 
  dwMilliseconds: DWORD       // Период ожидания 
): DWORD; stdcall;

    Эта функция возвращает одно из следующих значений:

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

var
  Handles: array[0..1] of THandle;
  Reason: DWORD;
  RestIndex: Integer;
  .   .   .
Handles[0] := OpenMutex(SYNCHRONIZE, FALSE, 'FirstResource');
Handles[1] := OpenMutex(SYNCHRONIZE, FALSE, 'SecondResource');
// Ждем первый из объектов
Reason := WaitForMultipleObjects(2, @Handles, FALSE, INFINITE);
case Reason of
  WAIT_FAILED: RaiseLastWin32Error;
  WAIT_OBJECT_0, WAIT_ABANDONED_0:
    begin
      ModifyFirstResource;
      RestIndex := 1;
    end;
  WAIT_OBJECT_0 + 1, WAIT_ABANDONED_0 + 1:
    begin
      ModifySecondResource;
      RestIndex := 0;
    end;
  // значение WAIT_TIMEOUT возвращено быть не может
end;
// Теперь ожидаем освобождения следующего объекта
if WailForSingleObject(Handles[RestIndex], 
  INFINITE) = WAIT_FAILED then RaiseLastWin32Error;
// Дождались, модифицируем оставшийся ресурс
if RestIndex = 0 then ModifyFirstResource
else ModifySecondResource;

    Описанную выше технику можно применять, если вы точно знаете, что задержка ожидания объекта окажется небольшой. В противном случае ваша программа окажется "замороженной" и не сможет даже перерисовать свое окно. Если период задержки может оказаться значительным, необходимо дать программе возможность реагировать на сообщения Windows. Выходом может служить использование функций с ограниченным периодом ожидания (и их повторный вызов, в случае возвращения значения WAIT_TIMEOUT). Можно также использовать следующую функцию:

function MsgWaitForMultipleObjects(
nCount: DWORD;     // Количество объектов синхронизации 
var pHandles;      // Адрес массива объектов 
fWaitAll: BOOL;    // Необходимость ожидания всех
                   // или любого из объектов 
dwMilliseconds,    // Период ожидания 
dwWakeMask: DWORD  // Тип события, прерывающего ожидание 
): DWORD; stdcall;
Главное отличие этой функции от предыдущей - параметр dwWakeMask, который является комбинацией битовых флагов QS_XXX и задает типы сообщений, прерывающих ожидание функции независимо от состояния ожидаемых объектов. Например, маска QS_KEY позволяет прервать ожидание при появлении в очереди сообщения WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP или WM_SYSKEYDOWN, а маска QS_ PAINT - сообщения WM_PAINT. Полный список значений, допустимых для параметра dwWakeMask, имеется в документации по Windows SDK. Если для вызвавшего функцию потока в очереди появляются сообщения, соответствующие заданной маске, функция возвращает значение WAIT_OBJECT_0 + nCount. Получив это значение, ваша программа может обработать его и снова вызвать функцию ожидания.

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

procedure TForm1.Button1Click(Sender: TObject);
var
  PI: TProcessInformation;
  SI: TStartupInfo;
  Reason: DWORD;
  Msg: TMsg;
begin
  // Инициализируем структуру TstartupInfo
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  // Запускаем внешнюю программу
  Win32Check(CreateProcess(nil, 'COMMAND.COM', nil,
    nil, False, 0, nil, nil, SI, PI));
//**************************************************
// Попробуйте заменить нижеприведенный код на строку
// WaitForSingleObject(PI.hProcess, INFINITE);
// и посмотреть, как будет реагировать программа на
// перемещение других окон над ее окном
//**************************************************
  repeat
    // Ожидаем завершения дочернего процесса или сообщения 
    // перерисовки WM_PAINT
    Reason := MsgWaitForMultipleObjects(1, 
    PI.hProcess, False, INFINITE, QS_PAINT);
    if Reason = WAIT_OBJECT_0 + 1 then begin
      // В очереди появилось сообщение WM_PAINT - Windows
      // требует обновить окно программы.
      // Удаляем сообщение из очереди
      PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE);
      // И перерисовываем наше окно
      Update;
    end;
    // Повторяем цикл, пока не завершится дочерний процесс
  until Reason = WAIT_OBJECT_0;
  // Удаляем из очереди накопившиеся там сообщения
  while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do;
  CloseHandle(PI.hProcess);
  CloseHandle(PI.hThread)
end;
Текст этого приложения можно взять здесь (202,5 Кб).


Рис.1. Результат работы исходного приложения


Рис.2. Результат работы приложения с заменой исходного текста

    Если в потоке, вызывающем функции ожидания явно (с помощью функции CreateWindow) или неявно (используя компонент TForm или технологию DDE или СОМ), создаются окна Windows, поток должен обрабатывать сообщения. Поскольку широковещательные сообщения посылаются всем окнам в операционной системе, поток, не обрабатывающий сообщения, может вызвать взаимную блокировку (операционная система ждет, когда поток обработает сообщение, поток - когда операционная система или другие потоки освободят объект) и привести к зависанию Windows. Если в вашей программе имеются подобные фрагменты, необходимо использовать функцию MsgWaitForMultipleObjects или MsgWaitForMultipleObjectsEx и допускать прерывание ожидания для обработки сообщений. Алгоритм аналогичен вышеприведенному примеру.

    На следующем шаге мы рассмотрим прерывание ожидания по запросу .




Предыдущий шаг Содержание Следующий шаг