Delphi TThread 后代 return 结果
Delphi TThread descendant return result
情况。我用一些 classes 创建了一个单元来解决代数问题(同余和系统),我给你看代码:
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
,因为此 class 有一个 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 的值,并且由于 var
的副作用,我预计 result
也会更新。为什么这没有发生?
我做了另一个测试,像这样改变构造函数:
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
但我不确定如何使用它。
让我们假设一个 class 字段 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
一起工作的原因是 classes 是引用类型 - 它们的变量不保存值,而是指向内存中对象的地址。当你复制一个 class 变量时,你只是在复制一个引用(即:一个指针),而对于值类型,变量的内容是在赋值时复制的。
话虽如此,没有什么可以阻止您将参数类型设为 var x : integer
并且 在构造函数中引用 :
constructor TCongruenceSystem.Create(var result: Integer);
begin
inherited Create(True);
FreeOnTerminate := true;
resInner := @result; {take the reference here}
end;
但这给调用者的印象是,一旦构造函数完成,您已经对您想要的变量进行了任何修改,并且他们可以自由处置整数。作为 PInteger
显式传递给调用者一个提示,即您的对象将保留对它们提供的整数的引用,并且需要确保在您的 class 处于活动状态时基础变量保持有效。
而且...尽管如此,我仍然从根本上不喜欢这个想法。通过采用这样的变量引用,您可以将非典型的生命周期管理问题卸载给调用者。传递指针最好在仅在传输点使用它们的地方完成。持有外部指针很麻烦,而且很容易出错。一个更好的方法是提供一个完成事件并让你的 class 的消费者附加一个处理程序。
例如 :
{ 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
。关于这个话题,请阅读这个问题(我会引用雷米...):
Ensure all TThread.Queue methods complete before thread self-destructs
Setting FreeOnTerminate := True in a queued method is asking for a memory leak.
Basically the result at the end of Execute
is just an integer number that has to be passed to the main thread.
I have read about ReturnValue
but I am not sure how to use it.
使用 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;
情况。我用一些 classes 创建了一个单元来解决代数问题(同余和系统),我给你看代码:
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
,因为此 class 有一个 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 的值,并且由于 var
的副作用,我预计 result
也会更新。为什么这没有发生?
我做了另一个测试,像这样改变构造函数:
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
但我不确定如何使用它。
让我们假设一个 class 字段 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
一起工作的原因是 classes 是引用类型 - 它们的变量不保存值,而是指向内存中对象的地址。当你复制一个 class 变量时,你只是在复制一个引用(即:一个指针),而对于值类型,变量的内容是在赋值时复制的。
话虽如此,没有什么可以阻止您将参数类型设为 var x : integer
并且 在构造函数中引用 :
constructor TCongruenceSystem.Create(var result: Integer);
begin
inherited Create(True);
FreeOnTerminate := true;
resInner := @result; {take the reference here}
end;
但这给调用者的印象是,一旦构造函数完成,您已经对您想要的变量进行了任何修改,并且他们可以自由处置整数。作为 PInteger
显式传递给调用者一个提示,即您的对象将保留对它们提供的整数的引用,并且需要确保在您的 class 处于活动状态时基础变量保持有效。
而且...尽管如此,我仍然从根本上不喜欢这个想法。通过采用这样的变量引用,您可以将非典型的生命周期管理问题卸载给调用者。传递指针最好在仅在传输点使用它们的地方完成。持有外部指针很麻烦,而且很容易出错。一个更好的方法是提供一个完成事件并让你的 class 的消费者附加一个处理程序。
例如 :
{ 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
。关于这个话题,请阅读这个问题(我会引用雷米...):
Ensure all TThread.Queue methods complete before thread self-destructs
Setting FreeOnTerminate := True in a queued method is asking for a memory leak.
Basically the result at the end of
Execute
is just an integer number that has to be passed to the main thread.I have read about
ReturnValue
but I am not sure how to use it.
使用 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;