Результат возврата потомка Delphi TThread

СИТУАЦИЯ. Я создал модуль с некоторыми классами для решения вопросов алгебры (сравнения и системы), я показываю вам код:

type
 TCongrError = class(Exception)
 end;

type
 TCongruence = class(TComponent)
  //code stuff
  constructor Create(a, b, n: integer); virtual;
 end;

type
 TCongrSystem = array of TCongruence;

type
 TCongruenceSystem = class(TThread)
  private
   resInner: integer;
   FData: TCongrSystem;
   function modinv(u, v: integer): integer; //not relevant
  protected
   procedure Execute; override;
  public
   constructor Create(data: TCongrSystem; var result: integer; hasClass: boolean);
 end;

Я решил использовать TThread, потому что у этого класса есть метод Execute, для завершения которого может потребоваться некоторое время из-за длины параметров, переданных конструктору. Вот реализация:

constructor TCongruenceSystem.Create(data: TCongrSystem; var result: integer; hasClass: boolean);
begin

 inherited Create(True);
 FreeOnTerminate := true;

 FData := data;
 setClass := hasClass;
 resInner := result;

end;

procedure TCongruenceSystem.Execute;
var sysResult, i, n, t: integer;
begin

 sysResult := 0;
 n := 1;

 //computation

 Queue( procedure
        begin
         ShowMessage('r = ' + sysResult.ToString);
         resInner := sysResult;
        end );

end;

ПРОБЛЕМА

Если вы посмотрите на Queue, вы увидите, что я использую (просто в качестве теста) ShowMessage, и он показывает правильное значение sysResult. Во второй строке, кстати, есть некоторые проблемы, которых я не могу понять.

Конструктор имеет var result: integer, поэтому я могу получить побочный эффект от переданной переменной, а затем я могу назначить resInner := result;. В конце (в очереди) я даю resInner значение sysResult и ожидаю, что result тоже будет обновлен из-за побочного эффекта var. Почему этого не происходит?

Я провел еще один тест, изменив конструктор следующим образом:

constructor TCongruenceSystem.Create(data: TCongrSystem; result: TMemo; hasClass: boolean);
//now of course I have resInner: TMemo

И изменив очередь на это:

Queue( procedure
        begin
         ShowMessage('r = ' + sysResult.ToString);
         resInner.Lines.Add(sysResult.ToString);
        end ); //this code now works properly in both cases! (showmessage and memo)

В конструкторе я передаю TMemo, который является ссылкой и в порядке, но разве исходный var result: integer не передается как ссылка? Почему тогда не работает?

Я хочу это сделать, потому что хочу сделать что-то вроде этого:

 //I put var a: integer; inside the public part of the TForm
 test := TCongruenceSystem.Create(..., a, true);
 test.OnTerminate := giveMeSolution;
 test.Start;
 test := nil;

Где giveMeSolution - это просто простая процедура, использующая переменную a, содержащую результат системы. Если это невозможно, что я могу сделать? По сути, результат в конце Execute - это просто целое число, которое нужно передать в основной поток.

Я читал о ReturnValue, но не уверен как это использовать.


person Alberto Miola    schedule 31.08.2017    source источник
comment
Это плохо спроектировано, и вам следует исправить это раньше, чем позже. Конечно, здесь ничего не должно происходить от TComponent. Это бессмысленно. Более серьезная проблема заключается в том, чтобы встроить в компонент нить. Этим должен заниматься потребитель кода.   -  person David Heffernan    schedule 31.08.2017
comment
@DavidHeffernan спасибо за ваш комментарий. Я хотел сделать из этого компонент; читая ваше предложение, я думаю, что мне следует удалить материал, связанный с потоками, и просто добавить функцию класса, которая решает проблему с помощью системы.   -  person Alberto Miola    schedule 31.08.2017
comment
Компонент бессмысленный. Это числовой класс. Вам это не нужно на поверхности дизайна.   -  person David Heffernan    schedule 31.08.2017


Ответы (2)


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

Я читал о ReturnValue, но не знаю, как его использовать.

Использовать свойство ReturnValue очень просто:

type
  TCongruenceSystem = class(TThread)
    ... 
  protected
    procedure Execute; override;
  public
    property ReturnValue; // protected by default
  end;

procedure TCongruenceSystem.Execute;
var
 ...
begin
  // computation
  ReturnValue := ...;
end;

test := TCongruenceSystem.Create(...);
test.OnTerminate := giveMeSolution;
test.Start;

....

procedure TMyForm.giveMeSolution(Sender: TObject);
var
  Result: Integer;
begin
  Result := TCongruenceSystem(Sender).ReturnValue;
  ...
end;
person Remy Lebeau    schedule 31.08.2017
comment
спасибо, это то, что я искал. Принимает ли возвращаемое значение только целые числа? Или также записи например? - person Alberto Miola; 31.08.2017
comment
@AlbertoMiola Только целое число. Если вы хотите вернуть более сложные данные, лучше всего использовать настраиваемое событие, как я показал в своем ответе. - person J...; 31.08.2017
comment
@AlbertoMiola: или, по крайней мере, через настраиваемые поля / свойства класса потока. - person Remy Lebeau; 31.08.2017
comment
@J ...: Это не влияет на обработчик событий OnTerminate, который запускается через Synchronize() перед уничтожением объекта потока. Обработчик событий сможет получить доступ к полям / свойствам объекта потока (как показано в моем ответе). Даже ваш собственный ответ использует это в своих интересах. - person Remy Lebeau; 31.08.2017
comment
@RemyLebeau Да, конечно - извините. Раньше просто отбрасывал ссылки на потоки, завершаемые без остановки. Забыл, что ты получил один обратно в Sender. Совершенно верно. - person J...; 31.08.2017

Предположим, поле класса FFoo : integer;;

 procedure TFoo.Foo(var x : integer);
 begin
   FFoo := x;
 end;

Здесь вы присваиваете значение x FFoo. Внутри метода Foo вы можете изменять значение переменной, переданной как x, но integers в противном случае являются типами значений, которые копируются при присваивании. Если вы хотите сохранить ссылку на внешнюю переменную integer, вам нужно будет объявить FFoo (или, в вашем случае, resInner) как PInteger (указатель на целое число). Например (упрощая):

TCongruenceSystem = class(TThread)
  private
    resInner: PInteger;       
  protected
    procedure Execute; override;
  public
    constructor Create(result: PInteger);
end;

куда

constructor TCongruenceSystem.Create(result: PInteger);
begin    
  inherited Create(True);
  FreeOnTerminate := true;      
  resInner := result;    
end;

который вы бы назвали test := TCongruenceSystem.Create(@a); и назначили:

 { ** See the bottom of this answer for why NOT to use }
 {    Queue with FreeOnTerminate = true **             }
 Queue( procedure
    begin
      ShowMessage('r = ' + sysResult.ToString);
      resInner^ := sysResult;
    end );

Причина, по которой он работает с TMemo, заключается в том, что классы являются ссылочными типами - их переменные не содержат значений, а скорее указывают на адрес объекта в памяти. Когда вы копируете переменную класса, вы копируете только ссылку (например, указатель), тогда как для типов значений содержимое переменной копируется при назначении.


С учетом сказанного, ничто не мешает вам сохранить аргумент, набранный как var x : integer, и взять ссылку в свой конструктор:

constructor TCongruenceSystem.Create(var result: Integer);
begin    
  inherited Create(True);
  FreeOnTerminate := true;      
  resInner := @result;   {take the reference here}   
end;

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

И ... несмотря на все сказанное, мне все еще принципиально не нравится эта идея. Принимая такую ​​ссылку на переменную, вы перекладываете на вызывающего абонента нетипичную проблему управления временем жизни. Указатели передачи лучше всего делать там, где они используются только в точке передачи. Держаться за чужой указатель беспорядочно, и слишком легко сделать ошибку. Гораздо лучшим подходом здесь было бы предоставить событие завершения и прикрепить обработчик к потребителю вашего класса.

Например:

  { define a suitable callback signature }
  TOnCalcComplete = procedure(AResult : integer) of object;

  TCongruenceSystem = class(TThread)
  private
    Fx, Fy : integer;
    FOnCalcComplete : TOnCalcComplete;
  protected
    procedure Execute; override;
  public
    constructor Create(x,y: integer);
    property OnCalcComplete : TOnCalcComplete read FOnCalcComplete write FOnCalcComplete;
  end;

constructor TCongruenceSystem.Create(x: Integer; y: Integer);
begin
  inherited Create(true);
  FreeOnTerminate := true;
  Fx := x;
  Fy := y;
end;

procedure TCongruenceSystem.Execute;
var
  sumOfxy : integer;
begin
  sumOfxy := Fx + Fy;
  sleep(3000);        {take some time...}
  if Assigned(FOnCalcComplete) then
    Synchronize(procedure
                begin
                  FOnCalcComplete(sumOfxy);
                end);
end;

Что бы вы затем назвали:

{ implement an event handler ... }
procedure TForm1.CalcComplete(AResult: Integer);
begin
  ShowMessage(IntToStr(AResult));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LCongruenceSystem : TCongruenceSystem;
begin
  LCongruenceSystem := TCongruenceSystem.Create(5, 2);
  LCongruenceSystem.OnCalcComplete := CalcComplete;  { attach the handler }
  LCongruenceSystem.Start;
end;

Вы также заметите, что я использовал здесь Synchronize вместо Queue. По этой теме, пожалуйста, прочтите этот вопрос (я процитирую Реми ...):

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

Установка FreeOnTerminate: = True в методе с очередью запрашивает утечку памяти.

person J...    schedule 31.08.2017
comment
Спасибо большое, я понял свою ошибку, наверное! Я думал, что с помощью var я смог скопировать значение И ссылку, а не только значение, но, конечно, я был неправ. - person Alberto Miola; 31.08.2017