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

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

Реализация каналов связи между фоновыми задачами.

И так мы научились обмениваться сообщениями между фоновой задачей и основным потоком программы. А что делать, если мы захотим обменяться сообщениями между различными фоновыми задачами. Ответ прост - мы должны создать дополнительный канал связи используя систему связи OTL. Оговорюсь сразу, подробное рассмотрение системы связи OTL будет дано отдельно, здесь же речь пойдет так сказать о практической реализации.

Рассмотрим создание дополнительного канала связи на демонстрационном примере tests\8_RegisterComm. За обеспечение механизма связи (создание и поддержку канала) в OTL отвечает интерфейс IOmniTwoWayChannel. Данный интерфейс обеспечивает реализацию канала связи между двумя конечными точками (Endpoint1 – канал связи – Endpoint2). Кончные точки представлены интерфейсом IOmniCommunicationEndpoint.
В OTL уже существует класс TOmniTwoWayChannel обеспечивающий поддержку интерфейса IOmniTwoWayChannel. Вот с ним мы и будем работать.
Итак, создадим канал связи (IOmniTwoWayChannel), используя функцию CreateTwoWayChannel. Также создадим две фоновые задачи, основанные на классе TOmniWorker. Каждая задача получила одну конечную точку нового канала связи в качестве параметра конструктора класса.
test_8_RegisterComm.pas         Создание дополнительного канала связи    
procedure TfrmTestRegisterComm.FormCreate(Sender: TObject);

begin

  FCommChannel := CreateTwoWayChannel(1024);

  FClient1 := CreateTask(TCommTester.Create(FCommChannel.Endpoint1)) // 1 точка

    .MonitorWith(OmniTED)

    .Run;

  FClient2 := CreateTask(TCommTester.Create(FCommChannel.Endpoint2)) // 2 точка

    .MonitorWith(OmniTED)

    .Run;

end;

Функция CreateTwoWayChannel создает объект TOmniTwoWayChannel, который отвечает за поддержку канала связи и возвращает интерфейсную ссылку на него (Более подробная информация о внутренней работе системы связи OTL представлена в разделе “Система связи”).
В обработчике нажатия кнопки “Send To Task 1” напишем код, реализующий отправку сообщения MSG_FORWARD и случайно сгенерированного числа в первую фоновую задачу.
test_8_RegisterComm.pas             
procedure TfrmTestRegisterComm.btnSendTo1Click(Sender: TObject);

var

  value: integer;

begin

  value := Random(100);

  Log(Format('Sending %d to task 1', [value]));

  FClient1.Comm.Send(MSG_FORWARD, value);

end;

В обработчике другой кнопки будет похожий код, только FClient1 будет заменен на FClient2. Класс TCommTester, реализующий фоновую задачу будет иметь обработчики сообщений для MSG_FORWARD и MSG_FORWARDING.
test_8_RegisterComm.pas            Класс TCommTester   
TCommTester = class(TOmniWorker)

  strict private

    // Конечная точка (для приема и передачи сообщений)

    ctComm: IOmniCommunicationEndpoint;

  public

    constructor Create(commEndpoint: IOmniCommunicationEndpoint);

    function  Initialize: boolean; override;

    // Получено сообщение от основного потока

    procedure OMForward(var msg: TOmniMessage); message MSG_FORWARD;

    // Получено сообщение от другого фонового потока

    procedure OMForwarding(var msg: TOmniMessage); message MSG_FORWARDING;

  end; { TCommTester }

Код в этом методе OMForward, во-первых уведомит GUI (основной поток программы), что сообщение MSG_FORWARD было получено и во-вторых отправит сообщение MSG_FORWARDING межзадачному каналу связи.
Обработчик MSG_FORWARDING (OMForwarding) только уведомит GUI, что сообщение было получено от другой фоновой задачи.
test_8_RegisterComm.pas           
procedure TCommTester.OMForward(var msg: TOmniMessage);

begin

  // Уведомим основной поток

  Task.Comm.Send(MSG_NOTIFY_FORWARD, msg.MsgData);

  // пошлее сообщение в дополнительный канал

  ctComm.Send(MSG_FORWARDING, msg.MsgData);

end;



procedure TCommTester.OMForwarding(var msg: TOmniMessage);

begin

  // Уведомим основной поток, что мы получили сообщение

  // от другой фоновой задачи

  Task.Comm.Send(MSG_NOTIFY_RECEPTION, msg.MsgData);

end;

Взаимодействии основного GUI  и двух потоков, связанных дополнительным каналом связи лучше всего понять по рисунке ниже:
Рис. Межпоточное взаимодействие с помощью дополнительного канала связи.
В следующем рисунке, Вы можете видеть, как значение 0 передалось от задачи 1 до задачи 2 и как значение 3 передалось от задачи 2 до задачи 1.
Рис. Передача сообщение от потока к потоку
Вроде просто, но не все. Основной фокус кроется в методе TCommTester.Initialize, где метод RegisterComm (ctComm) регистрирует конечную точку связи как дополнительный источник сообщений.
test_8_RegisterComm.pas            
function TCommTester.Initialize: boolean;

begin

  // Регистрируем конечную точку связи

  Task.RegisterComm(ctComm);

  Result := true;

end;

Наличия этого кода в инициализирующем методе класса фоновой задачи обеспечивает включения механизма отправки и приема сообщений, пребывающих в канал связи (ctComm) так же как это происходит с сообщениями, прибывающими в канал Comm. Фактически RegisterComm вы должны вызывать всегда, когда хотите задействовать механизм связи для дополнительного канала. Почему это так сделано и почему нужен принудительный вызов RegisterComm (и почему нет автоматической регистрации канала связи), вы узнаете более подробно далее в посте “Система связи”.

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

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