3

情况。我创建了一个包含一些类的单元来解决代数问题(同余和系统),我正在向您展示代码:

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;

WheregiveMeSolution只是一个简单的过程,它使用a包含系统结果的变量。如果这是不可能的,我该怎么办?基本上 Execute 结束时的结果只是一个整数,必须传递给主线程。

我已经阅读过ReturnValue但我不知道如何使用它。

4

2 回答 2

3

让我们假设一个类字段FFoo : integer;

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

在这里,您正在做的是分配to的。在方法内部,您可以自由修改传入的变量的值,否则是在赋值时复制的值类型。如果要保留对外部变量的引用,则需要将(或者,在您的情况下,)声明为(指向整数的指针)。例如(简化):xFFooFooxintegersintegerFFooresInnerPInteger

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;

但这给调用者的印象是,一旦构造函数完成,您已经对您想要的变量进行了任何修改,并且他们可以自由地处理整数。显式传递 asPInteger给调用者一个提示,即您的对象将保留对它们提供的整数的引用,并且需要确保基础变量在您的类处于活动状态时保持有效。

而且......尽管如此,我仍然从根本上不喜欢这个想法。通过接受像这样的变量引用,您正在将非典型的生命周期管理问题转移给调用者。传递指针最好在它们仅在传输点使用的地方完成。持有外来指针很麻烦,而且很容易发生错误。这里更好的方法是提供一个完成事件并让你的类的消费者附加一个处理程序。

例如

  { 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. 关于这个话题,请阅读这个问题(我会引用 Remy ...):

确保所有 TThread.Queue 方法在线程自毁之前完成

在队列方法中设置 FreeOnTerminate := True 要求内存泄漏。

于 2017-08-31T10:51:15.253 回答
3

基本上最后的结果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;
于 2017-08-31T16:38:06.043 回答