воскресенье, 26 сентября 2010 г.

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

Двухсторонняя связь. Подход 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 довольно часто.

2 комментария:

  1. Дмитрий, не совсем понятно, а где в классе TAsyncHello теперь находится, собственно говоря "рабочее тело метода"? То, что раньше находилось в методе TThread.Execute? Или теперь этот метод нужно запускать передачей внутрь соответствующего сообщения? Что-то типа - а теперь пора стартовать само рабочее тело... Или же вызов рабочего тела можно сделать из функции TAsyncHello.Initialize?

    ОтветитьУдалить
  2. А, разобрался... Можно вызвать нужную функцию с помощью метода Invoke... :) Красиво! :)

    ОтветитьУдалить