Как использовать шаблон конвейера в Delphi

Я пытаюсь реализовать шаблон конвейера в моем тестовом проекте ( Как сделать вызовы Mutlithreded idhttp для работы с StringList), но мне не удается адаптировать TThread код для кода шаблона конвейера. Существует не так много ресурсов о том, как его использовать.

Я старался изо всех сил ниже, пожалуйста, НЕ понижайте, я знаю, что мой код грязный, но я отредактирую свой вопрос, если необходимо.

type
  TForm2 = class(TForm)
    ...
  private
    procedure Retriever(const input: TOmniValue; var output: TOmniValue);
    procedure Inserter(const input, output: IOmniBlockingCollection);
    function HttpGet(url: string; var page: string): boolean;
  end;

procedure TForm2.startButton1Click(Sender: TObject);
var
  pipeline: IOmniPipeline;
  i       : Integer;
  v       : TOmniValue;
  s       : string;
  urlList : TStringList;
begin
  pipeline := Parallel.Pipeline;
  pipeline.Stage(Retriever);
  pipeline.Stage(Inserter).NumTasks(10);
  pipeline.Run;
  for s in urlList do
    pipeline.Input.Add(s);
  pipeline.Input.CompleteAdding;
  // wait for pipeline to complete
  pipeline.WaitFor(INFINITE);
end;

function TForm2.HttpGet(url: string; var page: string): boolean;
var
  lHTTP: TIdHTTP;
  i : integer;
  X : Tstrings;
  S,M,fPath : String;
begin
  lHTTP := TIdHTTP.Create(nil);
  X := TStringList.Create;
  try
    X.Text := lHTTP.Get('https://instagram.com/'+fPath);
    S:= ExtractDelimitedString(X.Text);
    X.Clear;
    Memo2.Lines.Add(fPath+ ' :     '+ M ); //how to pass the result to Inserter
  finally
    lHttp.Free;
  end;
end;

procedure TForm2.Inserter(const input, output: IOmniBlockingCollection);
var
  result   : TOmniValue;
  lpage     : string;
begin
  for result in input do begin
    Memo2.Lines.Add(lpage);
    FreeAndNil(lpage);
  end;
  // correect?
end;

procedure TForm2.Retriever(const input: TOmniValue; var output: TOmniValue);
var
  pageContents: string;
begin
  if HttpGet(input.AsString, pageContents) then
    output := //???
end;

1 ответ

Решение

Прежде всего - опишите, в чем заключается ваша конкретная проблема. Никто не может стоять за вашей спиной, смотреть на ваш компьютер и видеть, что вы делаете. http://www.catb.org/esr/faqs/smart-questions.html

Вы подразумеваете, что ваша программа ведет себя неправильно. Но вы не описываете как и почему. И мы этого не знаем.

Как общие замечания, вы немного злоупотребляете конвейером.

  1. все рабочие процедуры, которые вы передаете OTL - в вашем случае это Inserter а также Retriever работать в случайных темах. Это означает, что никто из них не должен касаться GUI без synchronizing - VCL не многопоточный. Также используя TThread.Synchronize это плохой выбор, как я объяснил вам в связанном вопросе. Это замедляет работу программы и делает формы нечитаемыми. Для обновления формы используйте опрос с фиксированной частотой кадров. Не обновляйте свою форму изнутри работников OTL.

Другими словами, Inserter это не то, что вам нужно. Все, что вам нужно из этого конвейера, это его коллекция Input, процедура загрузки и коллекция Output. Да, это очень простая задача для сложных вещей, поэтому я упомянул два других более простых шаблона.

Тебе нужно TTimer в вашей форме, которая будет опрашивать коллекцию Output с фиксированной частотой кадров 2-3 раза в секунду и проверять, что коллекция еще не завершена (если это так - конвейер остановлен), и это должно обновить GUI из основного потока.

  1. Вы не должны ждать завершения конвейера внутри вашего основного потока VCL. Вместо этого Вы должны отсоединить pipeleine и позволить ему работать полностью в фоновом режиме. Сохраните ссылку на созданный конвейер в переменную-член формы, чтобы вы могли получить доступ к ее коллекции Output из TTimer событие, а также может освободить конвейер после завершения процесса.

Вы должны держать эту переменную связанной с объектом конвейера, пока загрузка не закончится и не установите значение nil (Освободи объекты) после этого, но не раньше. Вы знаете об интерфейсах и подсчете ссылок в Delphi, верно?

Для других шаблонов OTL, таких как параллельный FOR, прочитайте документы OTL об их .NoWait() звонки.

  1. Вы должны сделать эту форму двухмодальной, чтобы иметь различный набор включенных элементов управления, когда загрузка выполняется, а когда нет. Я обычно делаю это со специальным логическим свойством, как я показал вам в теме, которую вы связали. Ваш пользователь не должен изменять списки и настройки во время выполнения конвейера (если вы не внедрили изменение задачи в реальном времени, но вы еще этого не сделали). Этот переключатель режимов также будет хорошим местом для освобождения готового объекта конвейера, когда переключение переходит из рабочего режима в режим ожидания.

  2. Если вы хотите поиграть с цепочкой работников конвейера, то вы можете поместить в коллекцию ввода не сами строки URL, а их массив - Memo1.Lines.ToArray(), затем вы можете начать с этапа Unpacker, который получает строковые массивы из коллекции входных данных (на самом деле их будет только один) и перечисляет их и помещает строки в коллекцию stage-output. Это, однако, имеет небольшое практическое значение, оно даже немного замедлит вашу программу, так как Memo1.Lines.ToArray() функция по-прежнему будет работать в основном потоке VCL. Но просто экспериментировать с конвейерами это может быть забавно.

Таким образом, проект становится таким,

 TfrmMain = class(TForm)
  private
    var pipeline: IOmniPipeline;

    property inProcess: Boolean read ... write SetInProcess;
...
  end.

procedure Retriever(const input: TOmniValue; var output: TOmniValue);
var
  pageContents, URL: string;
  lHTTP: TIdHTTP;
begin
  URL := input.AsString;

  lHTTP := TIdHTTP.Create(nil);
  try
    lHTTP.ReadTimeout := 30000;
    lHTTP.HandleRedirects := True;

    pageContents := ExtractDelimitedString( lHTTP.Get('https://instagram.com/' + URL) );

    if pageContents > '' then
       Output := pageContents;
  finally
    lHTTP.Destroy;
  end;
end;

procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if InProgress then begin
     CanClose := False;
     ShowMessage( 'You cannot close this window now.'^M^J+
                  'Wait for downloads to complete first.' ); 
  end;
end;

procedure TfrmMain.SetInProcess(const Value: Boolean);
begin
  if Value = InProcess then exit; // form already is in this mode

  FInProcess := Value;

  memo1.ReadOnly := Value;
  StartButton.Enabled := not Value;
  if Value then 
     Memo2.Lines.Clear;

  Timer1.Delay := 500; // twice per second
  Timer1.Enabled := Value;

  If not Value then  // for future optimisation - make immediate mode change 
     FlushData;      // when last worker thread quits, no waiting for timer event

  If not Value then
     pipeline := nil; // free the pipeline object

  If not Value then
     ShowMessage('Work complete');
end;

procedure TfrmMain.Timer1Timer(const Sender: TObject);
begin
  If not InProcess then exit;

  FlushData;

  if Pipeline.Output.IsFinalized then
     InProcess := False;
end;

procedure TForm2.startButton1Click(Sender: TObject);
var
  s       : string;
  urlList : TStringList;
begin
  urlList := Memo1.Lines;

  pipeline := Parallel.Pipeline;

  pipeline.Stage(Retriever).NumTasks(10).Run;

  InProcess := True; // Lock the input data GUI - user no more can edit it
  for s in urlList do
    pipeline.Input.Add(s);
  pipeline.Input.CompleteAdding;
end;

procedure TfrmMain.FlushData;
var v: TOmniValue;
begin
  if pipeline = nil then exit;
  if pipeline.Output = nil then exit;
  if pipeline.Output.IsFinalized then
  begin
    InProcess := False;  
    exit;
  end;

  Memo2.Lines.BeginUpdate;
  try
    while pipeline.Output.TryTake(v) do
      Memo2.Lines.Add( v.AsString );
  finally
    Memo2.Lines.EndUpdate;
  end;

  // optionally - scroll output memo2 to the last line 
end;

Обратите внимание на некоторые детали, подумайте о них и поймите суть этих:

  1. Только FlushData обновляет выходную заметку. FlushData вызывается из TTimer событие или из установщика свойства режима формы. Оба они всегда вызываются из основного потока VCL. таким образом FlushData НИКОГДА не называется фоновые потоки формы.

  2. Retriever это бесплатная автономная функция, она не является членом формы и ничего не знает о форме и не имеет ссылки на ваши экземпляры формы. Таким образом вы достигаете обеих целей: вы избегаете "тесной связи" и избегаете возможности ошибочного доступа к элементам управления формы из фонового потока, что недопустимо в VCL. Функции ретривера работают в фоновых потоках, они загружают данные, они хранят данные, но они никогда не касаются GUI. Это идея.

Практическое правило - все методы формы вызываются только из основного потока VCL. Все подпрограммы стадии конвейера - тела фоновых потоков - объявляются и работают вне любых форм VCL и не имеют доступа ни к одной из них. Не должно быть никакого смешения между этими сферами.

  1. Вы ограничиваете обновление графического интерфейса до фиксированной частоты обновления. И этот показатель должен быть не слишком частым. Windows GUI и пользовательские глаза должны успеть наверстать упущенное.

  2. Ваша форма работает в двух четко обозначенных режимах: InProcess а также not InProcess, В этих режимах пользователю доступны различные наборы функций и элементов управления. Он также управляет переходами между режимами, такими как очистка текста выходной заметки, оповещение пользователя об изменениях состояния, освобождение памяти используемых объектов управления потоками (здесь: конвейеры) и т. Д. Следовательно, изменяется только это свойство (вызывается установщик) из основного потока VCL, а не из фоновых рабочих. И № 2 тоже помогает в этом.

  3. Возможное будущее улучшение будет заключаться в использовании pipeline.OnStop событие, чтобы выпустить PostMessage с пользовательским сообщением Windows в вашей форме, так что он будет переключать режим сразу же после завершения работы, не ожидая следующего события olling. Это может быть ЕДИНСТВЕННОЕ место, где конвейер знает что-либо о форме и имеет какие-либо ссылки на нее. Но это открывает банку сообщений Windows, HWND-отдыха и других тонких вещей, которые я не хочу здесь описывать.

Другие вопросы по тегам