2016-11-29 175 views
1

方案1)當我點擊按鈕1,然後關閉形式X,而線程工作,我得到「主題錯誤:句柄無效」如何避免使用Thread.terminate命令崩潰?

方案2)當我關閉應用程序,而點擊按鈕1,我得到「訪問衝突。 ..「

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    ProccesSupervisor:= TMyThread0.Create(True); 
    ProccesSupervisor.FreeOnTerminate:=true; 
    ProccesSupervisor.Priority := tpNormal; 
    ProccesSupervisor.Resume; 
end; 

procedure TMyThread0.Execute; 
begin 
    repeat 

    //some code here 

    until ProccesSupervisor.terminated=true; 
end; 

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); 
begin 
    ProccesSupervisor.Terminate; 
    ProccesSupervisor.WaitFor; 
end; 
+0

什麼是「一些代碼在這裏」在做什麼? – Blorgbeard

+0

使用'嘗試除外' – Sami

+0

@Blorgbeard即使簡單的睡眠(100)它崩潰。 @ Sami它必須比簡單的嘗試更優雅的方式,除了 –

回答

5

從不引用線程對象時FreeOnTerminate = true。該線程可能已經完成了自己的工作並銷燬了自己,因此訪問它可能不安全。

在您的OnCloseQuery事件處理程序中,如果尚未點擊Button1,您還正在訪問未初始化的對象。

如果要控制線程的生命週期,請保留FreeOnTerminate = false

在您的OnCloseQuery事件處理程序中,檢查線程在終止之前是否已分配,並且還要防止單擊事件一次啓動多個線程。

TMyThread0.Execute()中,在訪問類的字段和方法時,不能有對特定線程實例的引用。而不是這樣寫:

until Terminated; 
+0

好的。謝謝大家的寶貴意見。現在一切正常。 我希望這是你如何檢查線程是否被初始化。 if ProccesSupervisor <>然後ProccesSupervisor.Terminate; –

+0

是的,沒關係。你也可以使用'if Assigned(ProccesSupervisor)然後...' –

3

不要TThread.FreeOnTerminate=True使用TThread.WaitFor()

Execute()退出時,如果TThread.FreeOnTerminate=True那麼TThread對象自行破壞,關閉TThread.WaitFor()等待的線程句柄。所以你可能看到「無效句柄」錯誤。或者您可能會遇到訪問衝突,或者由於競爭條件造成未定義的行爲,或者WaitFor()可能會在無效對象上被調用,或者通常該對象被銷燬,而WaitFor()爲仍在運行。並且WaitFor()會在任何操作系統錯誤(包括「無效句柄」錯誤)上引發異常。

設置TThread.FreeOnTerminate=True主要用於與一旦被啓動時被遺忘的線程一起使用。如果在啓動後需要引用線程,請根本不要使用FreeOnTerminate。你不希望線程消失在你的背後。

而且,Execute()不應該通過外部對象指針訪問其Terminated屬性。改用Self指針。

試試這個:

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    ProccesSupervisor := TMyThread0.Create(False); 
end; 

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); 
begin 
    if ProccesSupervisor <> nil then 
    begin 
    ProccesSupervisor.Terminate; 
    ProccesSupervisor.WaitFor; 
    FreeAndnil(ProccesSupervisor); 
    end; 
end; 

procedure TMyThread0.Execute; 
begin 
    while not Terminated do 
    begin 
    //some code here 
    end; 
end; 

如果你絕對必須設置TThread.FreeOnTerminate=True,那麼你應該使用TThread.OnTerminate事件,知道什麼時候該線程消失,但仍TThread.WaitFor()走就走,做自己的錯誤處理,如:

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    ProccesSupervisor := TMyThread0.Create(True); 
    ProccesSupervisor.FreeOnTerminate := True; 
    ProccesSupervisor.OnTerminate := ThreadTerminated; 
    ProccesSupervisor.Resume; 
end; 

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); 
var 
    H: array[0..1] of THandle; 
    Msg: TMsg; 
begin 
    if ProccesSupervisor <> nil then 
    begin 
    ProccesSupervisor.Terminate; 
    //ProccesSupervisor.WaitFor; 

    H[0] := ProccesSupervisor.Handle; 
    H[1] := Classes.SyncEvent; 
    WaitResult := 0; 
    repeat 
     case MsgWaitForMultipleObjects(2, H, False, INFINITE, QS_SENDMESSAGE) of 
     WAIT_OBJECT_0, WAIT_FAILED: Break; 
     WAIT_OBJECT_0 + 1: CheckSynchronize; 
     WAIT_OBJECT_0 + 2: PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE); 
     end; 
    until ProccesSupervisor = nil; 
    end; 
end; 

procedure TForm1.ThreadTerminated(Sender: TObject); 
begin 
    ProccesSupervisor := nil; 
end; 

procedure TMyThread0.Execute; 
begin 
    while not Terminated do 
    begin 
    //some code here 
    end; 
end; 
+0

第二個例子很麻煩。如果胎面將在FormCloseQuery中間終止,則可能會有麻煩,因爲沒有使用臨界區來保護ProccesSupervisor指針。最好不要使用FreeOnTerminate。可惜Borland已經讓這件事變得麻煩的FreeOnTerminate。 –

+1

@MaximMasiutin:因爲'FormCloseQuery()'和'ThreadTerminated()'在主UI線程中運行('OnTerminate'使用'TThread.Synchronize()')調用,所以沒有問題,所以代碼不需要保護對'ProccesSupervisor'指針的訪問。指針值可以改變的唯一時間是在CheckSynchronize()調用期間(當處理掛起的TThread.Synchronize()調用時),並且該循環處理該條件... –

+1

@MaximMasiutin ... by在再次調用'MsgWaitForMultipleObjects()'之前檢查指針是否被'ThreadTerminate()'改變了。即使線程對象在ThreadTerminated()退出後被銷燬,循環通過檢查'MsgWaitForMultipleObjects()'是否返回WAIT_OBJECT_0(線程已終止,但句柄尚未關閉)或WAIT_FAILED(線程句柄已關閉) )。在輸入「FormCloseQuery()」和調用「CheckSynchronize()」之間,'ProccesSupervisor'的值不能改變。此外'ProccesSupervisor.Handle'事先被緩存以確保安全。 –