пятница, 24 сентября 2010 г.

Использование OmniThread Libray (OTL) для создания многопоточных приложений - 3

Двухсторонняя связь. Подход 1

Итак, в предыдущем разделе мы разобрались, как осуществлять одностороннюю связь между фоновой задачей (потоком) и основным потоком программы. Теперь мы разберемся, как с помощью OTL передавать сообщения и данные из основного потока программы в фоновые потоки, созданные CreateTask.

Откроем пример tests\2_TwoWayHello, на основе которого мы разберем как реализовывается данная задача в рамках библиотеки OTL.
Рис.Реализация двухсторонней связи

Как видно на рисунке на форме примера есть три кнопки:
- кнопка “Start Hello”запускает фоновую задачу
- кнопка “Change Message” посылает сообщение MSG_CHANGE_MESSAGE фоновой задаче (причем каждый раз, при нажатии посылаются разные данные, которые генерируются произвольным образом).
Ну и последняя кнопка останавливает запущенную фоновую задачу. На форме также присутствуют компоненты TOmniTaskEventDispatch и TActionList.
test_2_TwoWayHello.pas    Запуск фоновой задачи
procedure TfrmTestTwoWayHello.actStartHelloExecute(Sender: TObject);
begin
  FHelloTask :=
    FMessageDispatch.Monitor(CreateTask(RunHello, 'Hello'))
    .SetParameter('Delay', 1000)
    .SetParameter('Message', 'Hello')
    .Run;
end;

Как видите создание фоновой задачи происходит уже в уже знакомой манере (CreateTask внутри метода Monitor). Обратите внимание на метод SetParameter интерфейса IOmniTaskControl. Первый параметр данного метода название параметра, второй -  значение Variant-типа.
В нашем примере мы запускаем фоновую задачу и передаем в нее два параметра. Более полную информацию о  способах создания фоновой задачи мы рассмотри в следующем разделе.
Обратите внимание, что FHelloTask  определен в области видимости strict private класса TfrmTestTwoWayHello. В этой переменной мы сохраняем  интерфейс контроля над нашей созданной (и запущенной) фоновой задачей, он нам пригодится.
Остановка задачи тривиальна:
test_2_TwoWayHello.pas    Остановка фоновой задачи.
procedure TfrmTestOTL.actStopHelloExecute(Sender: TObject);
begin
  FHelloTask.Terminate;
  FHelloTask := nil;
end;

Как Вы помните, обмен сообщениями осуществляется через  метод Comm. Ну и соответственно, так как мы запомнили интерфейс контроля нашей фоновой задачи, то мы посылаем сообщение ей по тому же принципу как посылали ранее, используя метод Send. Нажимая кнопкуChange Message” мы  генерируем сообщение MSG_CHANGE_MESSAGE которое должно быть потом обработано нашей фоновой задачей.
test_2_TwoWayHello.pas    Посылаем сообщение нашей фоновой задаче
procedure TfrmTestTwoWayHello.actChangeMessageExecute(Sender: TObject);
begin
  FHelloTask.Comm.Send(MSG_CHANGE_MESSAGE, WideString('Random ' +
                       IntToStr(Random(1234))));
end;
Ну хорошо, послать сообщение мы послали, а как-же нам его обработать. Ведь мы не можем использовать обработчик OnTaskMessage компонента TOmniEventMonitor. Он ведь работает для основного потока программы и не может использоваться в фоновом потоке. Все верно, для этого в методе нашей фоновой задачи мы должны осуществить мониторинг данного сообщения вручную. А как это сделать мы сейчас рассмотрим.
test_2_TwoWayHello.pas    Обработка сообщений в фоновом потоке
procedure RunHello(const task: IOmniTask);
var
  msg    : string;
  msgData: TOmniValue;
  msgID  : word;
begin
  msg := task.ParamByName['Message'];
  repeat
    case DSiWaitForTwoObjects(task.TerminateEvent,
                              task.Comm.NewMessageEvent, false,
                              task.ParamByName['Delay']) of
      WAIT_OBJECT_1:
        begin
          while task.Comm.Receive(msgID, msgData)
          do begin
            if msgID = MSG_CHANGE_MESSAGE then
              msg := msgData;
          end;
        end;
      WAIT_TIMEOUT:
        task.Comm.Send(0, msg);
      else
        break; //repeat
    end;
  until false;
end;

Внимательный читатель обратит внимание, что данный обработчик чем-то похож на обработку очередей событий в Windows и будет прав, обрабатывать наше сообщение мы будем по тому же принципу с помощью событий.
Фоновая задача, выполняющаяся в методе RunHello входит в бесконечный цикл в котором ожидается приход события.
Событие – это самый удобный способ передать сигнал ожидающему потоку, что некое событие свершилось (потому оно так и называется), и можно делее продолжать работу.
Функция DSiWaitForTwoObjects это обертка над обычной функцией API WaitForMultipleObjects реализующей ожидающую очередь для событий.
В нашем случае DSiWaitForTwoObjects ожидает прихода событий TerminateEvent или Comm.NewMessageEvent..
Событие Task.TerminateEvent  возникает, когда  пользователь щелкает кнопкой "Stop Hello".
Событие  Task.Comm.NewMessageEvent сигнализирует фоновой задаче, что новое сообщение в очереди сообщений ждет своей обработки. В этом случае DSiWaitForTwoObjects  (а на самом деле WaitForMultipleObjects) вернет  значение равное константе WAIT_OBJECT_1.
В том случае, когда в отмеченное состояние перешел объект-событие Task.TerminateEvent функция DSiWaitForTwoObjects возвращает значение WAIT_OBJECT_0. Но так как нам не нужно обрабатывать это событие, то в нашем случае выполняющийся код просто убежит из цикла repeat..until.
Если ни одно из событий не произошло в заданный период времени (как раз наш параметр task.ParamByName['Delay']) то DSiWaitForTwoObjects возвращает константу WAIT_TIMEOUT .
Стоит отметить, что в качестве таймаута можно указать константу INFINITE, и тогда функция будет ждать «до упора», а можно наоборот 0, и тогда поток вообще не будет приостановлен. В последнем случае функция вернет управление немедленно, но по ее результату можно будет узнать состояние объектов. Последний прием используется очень часто.
В нашем случае при WAIT_TIMEOUT мы просто отправляем в основной поток обратное сообщение.
Как вы видите для многопоточного программирования  использование циклов  для обработки событий довольно стандартный подход. Однако OTL не ограничивается только этим подходом, в дальнейшем мы перепишем этот код намного более простым способом.

Комментариев нет:

Отправить комментарий