2017-08-31 40 views
4

狀態。我已經創建了一個單元的一些類來解決代數的東西(同餘和系統),我顯示你的代碼:德爾福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因爲這個類有一個執行可能需要一些時間的方法由於傳遞給構造函數的參數的長度而結束。下面是執行:

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; 

問題

如果你看你看,我使用(就像測試)的ShowMessage和它顯示的sysResult正確的值Queue。順便提一下,第二行有一些我無法理解的問題。

構造函數有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但我不確定如何使用它。

+0

這個設計很糟糕,你應該早比晚解決它。當然這裏沒有任何東西應該來自TComponent。這沒有意義。更大的問題是在該組件中構建線程。這應該由代碼的使用者來處理。 –

+0

@DavidHeffernan感謝您的評論。我想用這個做一個組件;閱讀你的建議我認爲我應該刪除線程的東西,並簡單地添加一個系統解決問題的類函數。 –

+1

該組件毫無意義。這是一個數字類。你不需要它在設計表面上。 –

回答

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; 
+0

謝謝,這就是我一直在尋找的東西。返回值是否只接受整數?或者還記錄例如? –

+0

@AlbertoMiola只有一個整數。如果你想返回更復雜的數據,最好的方法就是通過一個自定義事件,就像我在我的答案中所展示的那樣。 –

+0

@AlbertoMiola:或至少通過線程類的自定義字段/屬性。 –

3

我們假設一個類字段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。關於這個話題,請有此問題的讀(我還要舉雷米......):

Ensure all TThread.Queue methods complete before thread self-destructs

設置FreeOnTerminate:在排隊的方法= True時,要求內存泄漏。

+0

非常感謝,我明白我的錯誤!我認爲,與VAR我能夠複製的價值和參考,不僅價值,但當然我錯了。 –