2009-01-31 51 views
2

我在OnTimer事件處理程序(TTimer)中收到異常,該異常在執行時會在父窗體中增加一個整數變量。定時器需要能夠訪問用作id的遞增整數。從OnTimer事件訪問父窗體中的變量 - 獲取異常

我的第一個問題是:我如何告訴Delphi 2007哪個代碼在哪個線程中運行?在調試模式下有沒有辦法檢查這個,所以我可以確定嗎?其次,如果我需要從另一個線程訪問和修改父表單中的變量,那麼最好的方法是什麼?看起來有時候Delphi允許我「不正確地」訪問這些變量而不會發出異常,而其他時候它會發出異常。

+0

你能否澄清一點,你得到哪些execeptions?你能顯示一些代碼嗎? – Harriv 2009-02-01 00:00:43

+0

您有兩個問題,請分開詢問。你會得到更好,更有針對性的答案,並且你將有機會獲得兩倍的聲望點。 – 2009-02-02 07:39:33

回答

5

可以肯定的是:一方面你說的是定時器事件,另一方面是關於多線程。這些是並行運行代碼的兩種完全不同的方式。

計時器將始終在主線程中運行。在那裏訪問在主線程中創建並使用的所有內容應該很安全。事實上,只有在沒有其他主線程代碼正在運行時纔會發生計時器事件,因爲它需要應用程序的消息處理程序來處理計時器消息。所以它不在任何事件處理代碼之外,或者當你的一個事件處理程序調用Application.ProcessMessages。

線程與此非常不同。在這種情況下,不同線程中的代碼彼此獨立運行。如果運行在多處理器機器(或多核)上,甚至可能真正並行運行。有很多問題可以用這種方式,特別是Delphi VCL(最多包括Delphi XE)不是線程保存,因此只能從主線程調用任何VCL類(有幾個例外這條規則)。

因此,請在期待有用答案之前,先澄清您是在討論定時器還是真正的多線程。

3

我怎樣才能告訴德爾福2007年哪個 代碼運行在哪個線程? 有調試模式下檢查 這樣我就可以確定嗎?

您可以設置斷點,並在執行停止時查看線程調試窗口。雙擊每個線程以在callstack調試窗口中查看其調用堆棧。您還可以使用Win32函數GetCurrentThreadId來查找當前線程(例如,用於日誌記錄,或確定當前線程是否爲主線程等)。

由於您沒有顯示任何代碼,因此很難更具體。只要確定:定時器事件處理程序中的代碼不會在不同的線程中執行。如果您只是使用定時器,而不是真正的後臺線程,則不會出現併發訪問問題。

其次,如果我需要訪問和修改 從 父表單變量另一個線程,什麼是做到這一點的最好辦法 ?有時候似乎是 德爾福允許我「錯誤地」訪問這些 變量,但不會給 一個例外,其他情況下 會給出例外。

如果你真的在另一個線程中訪問共享變量,你可以看到發生的各種事情,如果你不保護訪問。它可能在大多數時候都可以正常工作,或者獲得奇怪的值。如果您只想以線程安全的方式修改整數,請查看InterlockedIncrement。否則,你可以使用臨界區,互斥量,監視器...... JEDI在JclSynch單元中有一些有用的類。

3

你在問兩個問題,所以我會在兩個答案中回答他們。

你的第一個問題是關於使用TTimers;那些總是在主線程中運行。

很可能,您的異常是訪問衝突。

如果是,它通常是由以下任一:

  • A-你父窗體已經 破壞,當你TTimer火災。
  • b-當您的TTimer 發生火災時,您的父母表單尚未提供至 。

b很簡單:只需檢查您的參考是否爲

a更困難,取決於你如何引用你的父表單。

基本上你想確保你的引用在父類被銷燬或刪除時得到零。

如果你通過一個全局變量(通過窗體2在這個例子中)引用您的父窗體,那麼你應該有TForm2使窗體2變量使用像這樣的OnDestroy事件:

unit Unit2; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs; 

type 
    TForm2 = class(TForm) 
    procedure FormDestroy(Sender: TObject); 
    private 
    { Private declarations } 
    public 
    { Public declarations } 
    end; 

var 
    Form2: TForm2; 

implementation 

{$R *.dfm} 

procedure TForm2.FormDestroy(Sender: TObject); 
begin 
    Form2 := nil; 
end; 

end. 

如果您使用的是字段引用到父窗體(如FMyForm2Reference),那麼你應該使用添加通知方法是這樣的:

unit Unit1; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs, Unit2; 

type 
    TForm1 = class(TForm) 
    private 
    FMyForm2Reference: TForm2; 
    protected 
    procedure Notification(AComponent: TComponent; Operation: TOperation); override; 
    public 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

procedure TForm1.Notification(AComponent: TComponent; Operation: TOperation); 
begin 
    inherited Notification(AComponent, Operation); 
    if (Operation = opRemove) then 
    if (AComponent = FMyForm2Reference) then 
     FMyForm2Reference := nil; 
end; 

end. 

問候,

的Jeroen Pluimers

2

你問兩個問題,所以我會在兩個答案回答。

你的第二個問題是確保一次只有一個線程訪問一個表單中的一個變量。

由於該變量位於表單上,因此最好的方法是使用Synchronize方法。

有一個很好的例子,關於這個與Delphi一起發貨,它在thrddemo.dpr項目中,其中的單元在SortThds中。PAS有這種方法,顯示如何使用它:

procedure TSortThread.VisualSwap(A, B, I, J: Integer); 
begin 
    FA := A; 
    FB := B; 
    FI := I; 
    FJ := J; 
    Synchronize(DoVisualSwap); 
end; 

祝你好運,

的Jeroen Pluimers