Delphi не может выполнить процедуру в Task с объектными ссылками - PullRequest
0 голосов
/ 05 сентября 2018

У меня есть простой класс, который имеет следующую реализацию интерфейса.

Примечание : TPolyBase является абстрактным классом, а TPolyResult является array of double; не важно видеть их код, это не имеет значения здесь.

//INTERFACE

type
  TPolynomialList = class
    strict private
      FPolynomialList: TObjectList<TPolyBase>;
      FResult: TList<TPolyResult>;
      FCanGet: boolean;
      function GetResult: TList<TPolyResult>;
      procedure DoSolve;
    public
      constructor Create(PolynomialList: TObjectList<TPolyBase>);
      destructor Destroy; override;

      procedure SolvePolynomials(CompletionHandler: TProc);
      property Solutions: TList<TPolyResult> read GetResult;
  end;

//IMPLEMENTATION

constructor TPolynomialList.Create(PolynomialList: TObjectList<TPolyBase>);
begin
  FPolynomialList := PolynomialList;
  FResult := TList<TPolyResult>.Create;
  FCanGet := false;
end;

destructor TPolynomialList.Destroy;
begin
  FResult.Free;
  inherited;
end;

procedure TPolynomialList.DoSolve;
var
  i: integer;
begin
  for i := 0 to FPolynomialList.Count - 1 do
    FResult.Add(FPolynomialList[i].GetSolutions);

  FCanGet := true;
end;

function TPolynomialList.GetResult: TList<TPolyResult>;
begin
  if FCanGet = false then
    raise TEquationError.Create('You must solve the equation first!');

  Result := FResult;
end;

procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
    TTask.Run(procedure
              var
                ex: TObject;
              begin
                try
                  DoSolve;
                  TThread.Synchronize(nil, procedure
                                           begin
                                             CompletionHandler;
                                           end);
                except
                  on E: Exception do
                    begin
                      ex := AcquireExceptionObject;
                      TThread.Synchronize(nil, procedure
                                           begin
                                             Writeln( (ex as Exception).Message );  
                                           end);
                    end;
                end;
              end);
end;

Этот класс принимает список объектов в качестве входных данных и имеет внутреннее важное поле с именем FResult, которое предоставляет результаты пользователю. Доступ к нему можно получить из геттера, только если метод SolvePolynomials завершил свою работу.


Проблема в SolvePolynomials. Код, который я показал, использует задачу, потому что размер списка объектов может быть очень большим, и я не хочу замораживать пользовательский интерфейс. Почему я всегда получаю нарушение прав доступа в коде задачи?

Обратите внимание, что следующий код работает нормально, но это не то, что я хочу, потому что, если я введу 15000, программа остановится на несколько секунд.

procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
  DoSolve;
  CompletionHandler;
end;

Может быть проблема с переменной FPolynomialList? Если вы посмотрите на мой класс, единственное, что «взято извне» - это TObjectList<TPolyBase>, потому что в конструкторе я просто использую ссылку (я хотел бы избежать копирования в порядке 15k элементов). Все остальные переменные ни с кем не используются.

Я видел во многих книгах, которые я читал, например, «Delphi High Performance», что хорошо иметь задачу, которая вызывает внутренний «медленный» метод, но в этом случае могут быть ссылки, которые что-то портят. Любая идея?


Это код, который я использую в качестве теста:

var
 a: TObjectList<TPolyBase>;
 i, j: integer;
 f: TPolynomialList;
 s: string;

 function GetRandom: integer;
 begin
   Result := (Random(10) + 1);
 end;

begin
a := TObjectList<TPolyBase>.Create(true);
 try

   for i := 0 to 15000 do
     begin
       a.Add({*Descendant of TPolyBase*})
     end;


   f := TPolynomialList.Create(a);
   try
     f.SolvePolynomials(procedure
                        var
                          i, j: integer;
                        begin    
                          for i := 0 to f.Solutions.Count - 1 do
                            begin
                              for j := Low(f.Solutions[i]) to High(f.Solutions[i]) do
                                Writeln({output the results...})
                            end;  
                        end);
   finally
     f.Free;
   end;

 finally
   a.Free;
 end;

end.

1 Ответ

0 голосов
/ 05 сентября 2018

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

Вы должны переместить освобождение этих объектов в обработчик завершения.

По сути, ваш упрощенный код выглядит следующим образом:

type
  TPolynomialList = class
  public
    destructor Destroy; override;
    procedure DoSolve;
    procedure SolvePolynomials(CompletionHandler: TProc);
  end;

destructor TPolynomialList.Destroy;
begin
  Writeln('Destroyed');
  inherited;
end;

procedure TPolynomialList.DoSolve;
begin
  Writeln('Solving');
end;

procedure TPolynomialList.SolvePolynomials(CompletionHandler: TProc);
begin
  TTask.Run(
  procedure
  begin
    try
      DoSolve;
      TThread.Synchronize(nil,
        procedure
        begin
          CompletionHandler;
        end);
    except
      on E: Exception do
        Writeln(E.ClassName, ': ', E.Message);
    end;
  end);
end;

procedure Test;
var
  f: TPolynomialList;
begin
  f := TPolynomialList.Create;
  try
    f.SolvePolynomials(
      procedure
      begin
        Writeln('Solved');
      end);
  finally
    f.Free;
  end;
end;

Если вы запустите его, вы получите:

Destroyed
Solving
Solved

Однако, если вы переместите освобождение ваших переменных в обработчик завершения, порядок выполнения будет правильным.

procedure Test;
var
  f: TPolynomialList;
begin
  f := TPolynomialList.Create;
  f.SolvePolynomials(
    procedure
    begin
      Writeln('Solved');
      f.Free;
    end);
end;

Solving
Solved
Destroyed

Для вашего кода это означает перемещение и a.Free, и f.Free в обработчик завершения.

...