Delphi OmniThreadLibrary и Async - PullRequest
       54

Delphi OmniThreadLibrary и Async

1 голос
/ 11 марта 2019

Надеюсь, простой.Я использую цикл OTL Parallel.For для обработки большого количества данных.Объем данных может измениться, и если их много (это занимает более 2 секунд), Windows мерцает в форме заявки и дает временный статус «не отвечает» в строке заголовка.

Чтобы обойти это, я подумалЯ мог бы поместить процедуру с циклом Parallel.For в асинхронный вызов OTL, например

done:=false;
Async(ProcedureThatDoesParallelFor).Await(
procedure begin
done:=true;
end);
repeat application.processmessages until done=true;

. Это работает (или, кажется, работает), но может привести к тому, что программа просто прерывается / завершается без каких-либо сообщений об ошибках.Кажется, что это вызывает проблему «молчаливого прерывания», когда цикл Parallel.For выполняется очень быстро.

Если я отмечу приведенный выше код и возьму вызов MethodThatDoesParallelFor вне него, приложение будет работать нормально без неожиданного выхода,поэтому я предполагаю, что это должен быть асинхронный вызов, вызывающий проблему.Или комбинация Parallel.For в Async?

Является ли использование Async лучшим способом для запуска другой процедуры и ожидания ее завершения?Есть ли лучший OTL способ сделать это?

Спасибо за любые идеи или решения.

Вот самый простой пример, показывающий ошибку сбоя.Одиночная форма с памяткой и кнопкой.Нажмите кнопку, и программа будет зависать вокруг итерации 300.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,OtlParallel,OtlTaskControl;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure AsyncParallelFor;
var iterations:integer;
    blah:integer;
begin
     iterations:=10;
     //for iter:=0 to limit-1 do
     Parallel.For(0,iterations-1).Execute(procedure(iter:integer)
     var x,y:integer;
     begin
          for y:=0 to 50 do
          begin
               for x:=0 to 50 do
               begin
                    blah:=x+y;
               end;
          end;
     end);
end;

procedure AsyncProcedure;
var done:boolean;
begin
     done:=false;
     Parallel.Async(
          procedure
          begin
               //executed in background thread
               AsyncParallelFor;
          end,
          Parallel.TaskConfig.OnTerminated(
          procedure (const task: IOmniTaskControl)
          begin
               //executed in main thread after the async has finished
               done:=true;
          end
          )
     );
     //this point is reached immediately after the call to Async
     //the repeat loop waits until the Async is finished being signalled via done variable
     repeat
           application.processmessages;
     until done=true;
end;


procedure TForm1.Button1Click(Sender: TObject);
var iters:integer;
begin
     iters:=0;
     repeat
           memo1.lines.add('Iteration '+inttostr(iters)+'...');
           memo1.lines.add('Before Async');
           application.processmessages;
           AsyncProcedure;
           memo1.lines.add('After Async');
           application.processmessages;
           inc(iters);
     until 1>2;


end;

end.

AsyncParallelFor показывает основные вложенные циклы.Это просто простое дополнение для демонстрации проблемы.

AsyncProcedure выполняет вызов OTL Async и ожидает возврата.

У меня много непараллельного кода до и после вызова AsyncProcedureчто нужно дождаться окончания цикла параллельного. for.

Если я изменю щелчок кнопки, чтобы вызвать AsynParallelFor напрямую без Async, то зависание не будет.

1 Ответ

1 голос
/ 13 марта 2019

В вашем AsyncProcedure нет необходимости повторно ждать завершения асинхронного вызова. Это побеждает управляемую событиями модель , на которой построена ОС. Особый вызов Application.ProcessMessages может привести к неожиданным событиям.

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

Метод нажатия кнопки должен выполнять только короткую задачу, а не вечный цикл с ужасными вызовами Application.ProcessMessages.

Вместо этого используйте флаг, чтобы указать, должен ли быть выполнен новый вызов асинхронной процедуры.


Ниже приведен пример того, как изменить ваш тест с помощью метода обратного вызова и модели, управляемой событиями ( Я не пробовал вызовы OTL, но я был бы удивлен, если библиотека является причиной ваших проблем ):

type
  TForm1 = class(TForm)
    BtnStart: TButton;
    BtnStop: TButton;
    Memo1: TMemo;
    procedure BtnStartClick(Sender: TObject);
    procedure BtnStopClick(Sender: TObject);
  private
  { Private declarations }
    fDoRepeat : Boolean;
    fIterations : Integer;
    procedure MyCallbackMethod(Sender : TObject);
  public
  { Public declarations }
  end;

procedure AsyncProcedure( MyCallbackMethod : TNotifyEvent);
begin
  Parallel.Async(
    procedure
    begin
      //executed in background thread
      AsyncParallelFor;
    end,
  Parallel.TaskConfig.OnTerminated(
    procedure (const task: IOmniTaskControl)
    begin
      //executed in main thread after the async has finished
      MyCallbackMethod(Nil);
    end)
  );
end;

procedure TForm1.MyCallbackMethod(Sender : TObject);
begin
  if (Sender = nil) then // Callback from AsyncProcedure
     memo1.lines.add('After Async');
  if fDoRepeat then begin
    Inc(fIterations);
    memo1.lines.add('Iteration '+inttostr(fIterations)+'...');
    memo1.lines.add('Before Async');
    AsyncProcedure(MyCallbackMethod);        
  end;
end;

procedure TForm1.BtnStartClick(Sender: TObject);
begin
  fDoRepeat := true;
  fIterations := 0;
  BtnStart.Enabled := false;
  MyCallbackMethod(Sender);  // Start iteration event looping
end;

procedure TForm1.BtnStopClick(Sender: TObject);
begin
  fDoRepeat := false;  // Stop iteration loop
  BtnStart.Enabled := true;
end;

Обновление

Запуск вышеуказанного теста в режиме отладки дал:

Недостаточно памяти

после 387 итераций в модуле OTL, выделяющем память для буфера (и он работает медленно).

Тестирование OTL Parallel.For() с некоторыми другими примерами из Обновление индикатора выполнения из параллельного цикла (плюс два бонуса) не улучшило результат. Программа зависает на 400 итерациях.


Однако использование Delphi PPL с ошибками действительно сработало.

Uses
  Threading;

procedure AsyncParallelFor;
var
  iterations:integer;
  blah:integer;
begin
  iterations := 10;
  TParallel.For(0,iterations-1,
    procedure(iter : integer)
    var x,y:integer;
    begin
      for y := 0 to 50 do
      begin
        for x := 0 to 50 do
        begin
          blah := x+y;
        end;
      end;
    end);
end;

procedure AsyncProcedure( MyCallbackMethod : TNotifyEvent);
begin
  TTask.Run(
    procedure
    begin
      AsyncParallelFor;
      //executed in main thread after the async has finished
      TThread.Queue(nil,
        procedure
        begin
          MyCallbackMethod(Nil);
        end
      );
  end);
end;

Чтобы обновить графический интерфейс в параллельном цикле for, просто используйте этот код внутри цикла:

TThread.Queue(nil,
  procedure 
  begin
    // Some code that updates the GUI or calls a method to do so.
  end
);
...