Двухсторонняя связь. Подход 2
В данном разделе мы уйдем от метода написания цикла ожидающего прихода событий (сообщений). Конечно, цикл никуда не денется, но он будет спрятан в самой библиотеке OTL на низком уровне, а если быть точным, то в модуле OtlTask.pas.
Для этого откроем демонстрационный пример tests\5_TwoWayHello_without_loop и посмотрим, как можно переделать наш предыдущий пример проще и красивей.
На сей раз код фоновой задачи, будет заключен не в рабочую процедуру, а в отдельный объект. Этот объект должен обеспечивать интерфейс IOmniWorker или быть потомком класса TOmniWorker (который осуществляет поддержку IOmniWorker). Данный класс будет обеспечивать обработку двух сообщений MSG_CHANGE_MESSAGE и MSG_SEND_MESSAGE.
const
MSG_CHANGE_MESSAGE = 1;
MSG_SEND_MESSAGE = 2;
При приходе сообщения MSG_CHANGE_MESSAGE код, выполняющийся в фоновом потоке, изменит текст, хранящийся во внутренней переменной aiMessage, а сообщение MSG_SEND_MESSAGE заставит фоновый поток отправить значение этой переменной обратно основному потоку (точно так же как это сделано в предыдущем примере).
Запишем эти два обработчика сообщения, идентично способу, которым мы описываем обработчики сообщения Windows. Идентификаторы сообщений могут быть произвольные, автор библиотеки в примерах нумерует обычно с 1, я же на будущее все-таки рекомендую нумеровать с WM_USER+1.
test_5_TwoWayHello_without_loop.pas Класс для фоновой задачи
type
TAsyncHello = class(TOmniWorker)
strict private
aiMessage: string;
public
function Initialize: boolean; override;
procedure OMChangeMessage(var msg: TOmniMessage); message MSG_CHANGE_MESSAGE;
procedure OMSendMessage(var msg: TOmniMessage); message MSG_SEND_MESSAGE;
end;
function TAsyncHello.Initialize: boolean;
begin
aiMessage := Task.ParamByName['Message'];
Result := true;
end;
// Инициализация переменной
function TAsyncHello.Initialize: boolean;
begin
aiMessage := Task.ParamByName['Message'];
Result := true;
end;
// Обработчики сообщений
procedure TAsyncHello.OMChangeMessage(var msg: TOmniMessage);
begin
aiMessage := msg.MsgData;
end;
procedure TAsyncHello.OMSendMessage(var msg: TOmniMessage);
begin
Task.Comm.Send(0, aiMessage);
end;
Как вы видите код фоновой задачи простой, код же создания самой задачи немного изменен:
test_5_TwoWayHello_without_loop.pas Создание фоновой задачи
procedure TfrmTestOTL.actStartHelloExecute(Sender: TObject);
var
worker: IOmniWorker;
begin
worker := TAsyncHello.Create;
FHelloTask :=
OmniTaskEventDispatch1.Monitor(CreateTask(worker, 'Hello')).
SetTimer(1000, MSG_SEND_MESSAGE).
SetParameter('Delay', 1000).
SetParameter('Message', 'Hello').
Run;
end;
В данном коде мы создаем объект TAsyncHello (реализующий нашу фоновую задачу) и храним его в интерфейсной переменной. Далее в CreateTasks передается данный интерфейс объекта, вместо рабочей процедуры. Параметры SetParameters используются так же, как в предыдущем примере.
Единственная интересная новая вещь здесь - вызов SetTimer. Данный вызов говорит OTL, что фоновой задаче нужно отправлять сообщение MSG_SEND_MESSAGE каждые 1000 миллисекунд. Использовать метод SetTimer нужно тогда, когда Вам нужно добиться периодического выполнения некоторого обработчика сообщения в фоновой задаче.
Вызов SetTimer представлен ниже:
SetTimer
procedure SetTimer(interval_ms: cardinal; timerMessageID: integer = -1); overload;
procedure SetTimer(interval_ms: cardinal; const timerMethod: pointer); overload;
procedure SetTimer(interval_ms: cardinal; const timerMessageName: string); overload;
Во второй параметр SetTimer мы можете передавать идентификатор сообщения, которое должно быть обработано в фоновой задаче. Другим способом вызова Вы можете передавать указатель на метод обработчика данного сообщения. Или имя метода обработчика, которое должно быть вызвано OTL при срабатывании таймера.
Когда второй параметр SetTimer не определен, вместо обработчика данного сообщения вызывается метод по умолчанию Timer интерфейса IOmniWorker.
В нашем примере метод SetTimer представлен только для показа такой возможности в OTL. Фактически сообщения в фоновую задачу можно посылать обычным способом, что и демонстрируется на примере MSG_CHANGE_MESSAGE.
test_5_TwoWayHello_without_loop.pas Отправка сообщения фоновой задаче
procedure TfrmTestOTL.actChangeMessageExecute(Sender: TObject);
begin
FHelloTask.Comm.Send(MSG_CHANGE_MESSAGE, 'Random ' + IntToStr(Random(1234)));
end;
Для завершения задачи вызывают FHelloTask.Terminate и обнуляют FHelloTask. Объект, реализующий фоновую задачу будет уничтожен автоматически. Такое уничтожение внутренних объектов OTL (через обнуление интерфейса на них) применяется в OTL довольно часто.
Дмитрий, не совсем понятно, а где в классе TAsyncHello теперь находится, собственно говоря "рабочее тело метода"? То, что раньше находилось в методе TThread.Execute? Или теперь этот метод нужно запускать передачей внутрь соответствующего сообщения? Что-то типа - а теперь пора стартовать само рабочее тело... Или же вызов рабочего тела можно сделать из функции TAsyncHello.Initialize?
ОтветитьУдалитьА, разобрался... Можно вызвать нужную функцию с помощью метода Invoke... :) Красиво! :)
ОтветитьУдалить