вторник, 12 октября 2010 г.

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

Создание задачи

OTL как вы знаете, предлагает несколько различных способов создания задач.

Самый простой путь создания фоновой задачи был продемонстрирован в демонстрационном примере  1_AppWayHello, где в CreateTask в качестве параметра worker передается имя глобальной процедуры имеющий тип TOmniTaskProcedure.
Процедура должна быть определена в программе как
TOmniTaskProcedure = procedure(const task: IOmniTask);
то есть должна принимать один параметр типа IOmniTask
  . . .
CreateTask(RunHelloWorld, 'HelloWorld').Run;
  . . .
procedure RunHelloWorld(const task: IOmniTask);
begin
end;

Такой же подход используется и в нашем примере 2_TwoWayHello. Интересен этот способ также тем, что Вы можете объявить этот метод в том же самом классе, из которого вызывается CreateTask. Тем самым Вы можете обратиться к полям класса и методам из кода, выполняющегося в потоке. Только имейте в виду, что при таком обращении запрос будет идти от вторичного потока и перед Вами могут встать все те проблемы синхронизации потоков как при обычной работе с потоками! Фактически Вам в этом случае нужно задуматься о потокобезопасности данных, хранящихся в классе.
Однако такой подход Вы будете использовать крайне редко и для очень простых фоновых задач. Фактически при создании действительно грамотно спроектированных потоков (задач) вы должны использовать другой подход, который даст Вам полную мощь OTL при работе с фоновой задачей (внутренний ждущий цикл и диспетчеризация сообщений, контроль выполнения фоновой задачи и прочие вкусности).
Фактически Вы должны создать объект рабочей задачи (нашего фонового потока), используя интерфейс IOmniWorker. Рассмотрим на демонстрационном примере 5_TwoWayHello_without_loop процесс создания такой задачи:
test_5_TwoWayHello_without_loop   Создание задачи с использованием интерфейса IOmniWorker
procedure TfrmTestTwoWayHello.actStartHelloExecute(Sender: TObject);
var
  worker: IOmniWorker; {интерфейс !}
begin
  worker := TAsyncHello.Create;
  FHelloTask :=
    OmniEventMonitor1.Monitor(CreateTask(worker, 'Hello')).
    SetTimer(1000, MSG_SEND_MESSAGE).
    SetParameter('Delay', 1000).
    SetParameter('Message', 'Hello').
    Run;
end;

Так как Вы создаете класс (TAsyncHello), поддерживающий данный интерфейс с помощью языка программирования Delphi, то сам Delphi будет управлять временем жизни данного класса. Когда задача будет закончена, экземпляр данного класса автоматически будет уничтожен.
По этой же самой причине нет необходимости в создании предварительного экземпляра класса до вызова CreateTask. Класс, поддерживающий интерфейс IOmniWorker можно создать прямо в вызове CreateTask, как это продемонстрировано в примере 7_InitTest.
 test_7_InitTest.pas    Создание класса непосредственно в CreateTasks
  TInitTest = class(TOmniWorker)
  strict private
    itSuccess: boolean;
  public
    constructor Create(success: boolean);
    function  Initialize: boolean; override;
  end;
  . . .
  task := OmniEventMonitor1.Monitor(
                     CreateTask(TInitTest.Create(success), 'InitTest'))
                                    .SetPriority(tpIdle)
                                    .Run;

Поскольку функция CreateTask ожидает интерфейс, Вы никогда не должны передавать  в него переменную или поле, которое объявлено как объект. Следующий код  не будет работать:
Не делайте так
procedure TfrmTestTwoWayHello.actStartHelloExecute(Sender: TObject);
var
  worker: TAsyncHello; {класс, но не интерфейс, так код работать не будет }
begin
  worker := TAsyncHello.Create;
  FHelloTask :=
    OmniEventMonitor1.Monitor(CreateTask(worker, 'Hello')).
    SetTimer(1000, MSG_SEND_MESSAGE).
    SetParameter('Delay', 1000).
    SetParameter('Message', 'Hello').
    Run;
end;

Если Вы храните какую-то  информации в рабочей задаче (классе, наследуемом от TOmniWorker) и вам нужно получить ее во время ее выполнения из основного потока программы, Вы можете использовать преобразование интерфейса IOmniWorker назад к объекту. Рассмотрим это на демонстрационном примере 6_TwoWayHello_with_object_worker:
 test_6_TwoWayHello_with_object_worker.pas             Приведение к объекту
TAsyncHello = class(TOmniWorker)
strict private
    aiCount  : integer; {храним счетчик}
public
. . .
    property Count: integer read aiCount; {свойство к которому можно обратится}
end;
. . .

strict private
    FHelloTask: IOmniTaskControl;
private
    FWorker: IOmniWorker;
  . . .

procedure TfrmTestTwoWayHello.actStopHelloExecute(Sender: TObject);
begin
  FHelloTask.Terminate;
  FHelloTask := nil;
  lbLog.ItemIndex := lbLog.Items.Add(Format('%d Hello World''s sent',
   { Приведение к объекту для получения Count }
    [TAsyncHello(FWorker.Implementor).Count]));
end;

Кстати никогда не обнуляйте ссылку FHelloTask на интерфейс IOmniTaskControl  если вы хотите закончить задачу. Вы должны использовать Terminate  для завершения вашей задачи. Хоть по идее обнуление счетчика ссылок интерфейса должно привести к уничтожению объекта реализующего данный интерфейс я настоятельно не рекомендую делать так с IOmniTaskControl . С другими интерфейсами OTL это допустимо, но не с IOmniTaskControl. Возможно в будущем эта ситуация изменится.
Еще одна интересная особенность, если Вы используете дополнительные параметры в конструктора класса, который вызывается в функции CreateTask, Вы должны поместить пустую круглую скобку после. Create, иначе Язык программирования Delphi не будет компилировать Ваш код.
Следующий фрагмент иллюстрирует это:
test_14_TerminateWhen.pas          Хитрая скобка
Log(Format('Task started: %d',
  [CreateTask(TMyWorker.Create()  )  ||<- вот эта скобка
    .TerminateWhen(FTerminate).WithCounter(FCounter)
    .MonitorWith(OmniTED).Run.UniqueID]));

Мы разобрали три основных способа создания задач в OTL, но есть еще четвертый – основанный на анонимных методах (ANONYMOUS METHODS), которые появились в языке Delphi, начиная с версии 2009. Как следует из названия, анонимные методы представляют собой процедуры или функции, которые не имеют ассоциированного имени. Анонимный метод использует блок кода как единое целое, которое может быть присвоено переменной или быть использовано как параметр какого-либо метода. Тип анонимного метода декларируется как ссылка на метод. Библиотека OTL может использовать анонимные методы при создании фоновых задач. Пример создания фоновой задачи использующей анонимные методы продемонстрирован в примере tests\21_Anonymous_methods:
21_Anonymous_methods.pas          Анонимные метод в CreateTasks
procedure TfrmAnonymousMethodsDemo.btnHelloClick(Sender: TObject);
begin
  btnHello.Enabled := false;
  OTLMonitor.Monitor(CreateTask(
    // Анонимный метод
    procedure (const task: IOmniTask) begin
      task.Comm.Send(0, Format('Hello, world! Reporting from thread %d', [GetCurrentThreadID]));
    end,
    'HelloWorld')).Run;
end;
Мало того, библиотека OTL при использовании анонимных методов предоставляет интересные возможности в плане обработки сообщений а также параллельных вычислений. Но об этом позже.

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

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