Двухсторонняя связь. Подход 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 не ограничивается только этим подходом, в дальнейшем мы перепишем этот код намного более простым способом.
Комментариев нет:
Отправить комментарий