2011-10-31 43 views
4

我一個線程通常的設置是一個while循環和while循環中做兩件事情:如何終止一個線程?

  • 做了一些工作
  • 暫停,直到從外部
procedure TMIDI_Container_Publisher.Execute; 
begin 
    Suspend; 
    while not Terminated do 
    begin 
     FContainer.Publish; 
     if not Terminated then Suspend; 
    end; // if 
end; // Execute // 

恢復這工作正常。要終止我使用的代碼:

destructor TMIDI_Container_Publisher.Destroy; 
begin 
    Terminate; 
    if Suspended then Resume; 
    Application.ProcessMessages; 
    Self.WaitFor; 

    inherited Destroy; 
end; // Destroy // 

此Destroy在Windows 7中正常工作,但在XP中掛起。問題似乎是WaitFor,但是當我刪除它時,代碼掛起在inherited Destroy

任何人的想法是什麼錯?


更新2011/11/02 感謝大家的幫助。 Remy Labeau提供了一個代碼示例來避免Resume/Suspend。我將從現在開始在我的程序中實施他的建議。對於這個特定的情況,我受到了CodeInChaos的建議的啓發。只需創建一個線程,讓它在Execute中進行發佈並忘記它。我用Remy的例子來重寫我的一個定時器。我在下面發佈這個實現。

unit Timer_Threaded; 

interface 

uses Windows, MMSystem, Messages, SysUtils, Classes, Graphics, Controls, Forms, 
    Dialogs, SyncObjs, 
    Timer_Base; 

Type 
    TTask = class (TThread) 
    private 
     FTimeEvent: TEvent; 
     FStopEvent: TEvent; 
     FOnTimer: TNotifyEvent; 

    public 
     constructor Create; 
     destructor Destroy; override; 
     procedure Execute; override; 
     procedure Stop; 
     procedure ProcessTimedEvent; 

     property OnTimer: TNotifyEvent read FOnTimer write FOnTimer; 
    end; // Class: TWork // 

    TThreadedTimer = class (TBaseTimer) 
    private 
     nID: cardinal; 
     FTask: TTask; 

    protected 
     procedure SetOnTimer (Task: TNotifyEvent); override; 

     procedure StartTimer; override; 
     procedure StopTimer; override; 

    public 
     constructor Create; override; 
     destructor Destroy; override; 
    end; // Class: TThreadedTimer // 

implementation 

var SelfRef: TTask; // Reference to the instantiation of this timer 

procedure TimerUpdate (uTimerID, uMessage: cardinal; dwUser, dw1, dw2: cardinal); stdcall; 
begin 
    SelfRef.ProcessTimedEvent; 
end; // TimerUpdate // 

{******************************************************************* 
*                 * 
* Class TTask              * 
*                 * 
********************************************************************} 

constructor TTask.Create; 
begin 
    FTimeEvent := TEvent.Create (nil, False, False, ''); 
    FStopEvent := TEvent.Create (nil, True, False, ''); 

    inherited Create (False); 

    Self.Priority := tpTimeCritical; 
end; // Create // 

destructor TTask.Destroy; 
begin 
    Stop; 
    FTimeEvent.Free; 
    FStopEvent.Free; 

    inherited Destroy; 
end; // Destroy // 

procedure TTask.Execute; 
var two: TWOHandleArray; 
    h: PWOHandleArray; 
    ret: DWORD; 
begin 
    h := @two; 
    h [0] := FTimeEvent.Handle; 
    h [1] := FStopEvent.Handle; 

    while not Terminated do 
    begin 
     ret := WaitForMultipleObjects (2, h, FALSE, INFINITE); 
     if ret = WAIT_FAILED then Break; 
     case ret of 
     WAIT_OBJECT_0 + 0: if Assigned (OnTimer) then OnTimer (Self); 
     WAIT_OBJECT_0 + 1: Terminate; 
     end; // case 
    end; // while 
end; // Execute // 

procedure TTask.ProcessTimedEvent; 
begin 
    FTimeEvent.SetEvent; 
end; // ProcessTimedEvent // 

procedure TTask.Stop; 
begin 
    Terminate; 
    FStopEvent.SetEvent; 
    WaitFor; 
end; // Stop // 

{******************************************************************* 
*                 * 
* Class TThreaded_Timer           * 
*                 * 
********************************************************************} 

constructor TThreadedTimer.Create; 
begin 
    inherited Create; 

    FTask := TTask.Create; 
    SelfRef := FTask; 
    FTimerName := 'Threaded'; 
    Resolution := 2; 
end; // Create // 

// Stop the timer and exit the Execute loop 
Destructor TThreadedTimer.Destroy; 
begin 
    Enabled := False; // stop timer (when running) 
    FTask.Free; 

    inherited Destroy; 
end; // Destroy // 

procedure TThreadedTimer.SetOnTimer (Task: TNotifyEvent); 
begin 
    inherited SetOnTimer (Task); 

    FTask.OnTimer := Task; 
end; // SetOnTimer // 

// Start timer, set resolution of timesetevent as high as possible (=0) 
// Relocates as many resources to run as precisely as possible 
procedure TThreadedTimer.StartTimer; 
begin 
    nID := TimeSetEvent (FInterval, FResolution, TimerUpdate, cardinal (Self), TIME_PERIODIC); 
    if nID = 0 then 
    begin 
     FEnabled := False; 
     raise ETimer.Create ('Cannot start TThreaded_Timer'); 
    end; // if 
end; // StartTimer // 

// Kill the system timer 
procedure TThreadedTimer.StopTimer; 
var return: integer; 
begin 
    if nID <> 0 then 
    begin 
     return := TimeKillEvent (nID); 
     if return <> TIMERR_NOERROR 
     then raise ETimer.CreateFmt ('Cannot stop TThreaded_Timer: %d', [return]); 
    end; // if 
end; // StopTimer // 

end. // Unit: MSC_Threaded_Timer // 


unit Timer_Base; 

interface 

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

type 
    TCallBack = procedure (uTimerID, uMessage: UINT; dwUser, dw1, dw2: DWORD); 

    ETimer = class (Exception); 

{$M+} 
    TBaseTimer = class (TObject) 
    protected 
     FTimerName: string;  // Name of the timer 
     FEnabled: boolean;  // True= timer is running, False = not 
     FInterval: Cardinal; // Interval of timer in ms 
     FResolution: Cardinal; // Resolution of timer in ms 
     FOnTimer: TNotifyEvent; // What to do when the hour (ms) strikes 

     procedure SetEnabled (value: boolean); virtual; 
     procedure SetInterval (value: Cardinal); virtual; 
     procedure SetResolution (value: Cardinal); virtual; 
     procedure SetOnTimer (Task: TNotifyEvent); virtual; 

    protected 
     procedure StartTimer; virtual; abstract; 
     procedure StopTimer; virtual; abstract; 

    public 
     constructor Create; virtual; 
     destructor Destroy; override; 

    published 
     property TimerName: string read FTimerName; 
     property Enabled: boolean read FEnabled write SetEnabled; 
     property Interval: Cardinal read FInterval write SetInterval; 
     property Resolution: Cardinal read FResolution write SetResolution; 
     property OnTimer: TNotifyEvent read FOnTimer write SetOnTimer; 
    end; // Class: HiResTimer // 

implementation 

constructor TBaseTimer.Create; 
begin 
    inherited Create; 

    FEnabled := False; 
    FInterval := 500; 
    Fresolution := 10; 
end; // Create // 

destructor TBaseTimer.Destroy; 
begin 
    inherited Destroy; 
end; // Destroy // 

// SetEnabled calls StartTimer when value = true, else StopTimer 
// It only does so when value is not equal to the current value of FEnabled 
// Some Timers require a matching StartTimer and StopTimer sequence 
procedure TBaseTimer.SetEnabled (value: boolean); 
begin 
    if value <> FEnabled then 
    begin 
     FEnabled := value; 
     if value 
     then StartTimer 
     else StopTimer; 
    end; // if 
end; // SetEnabled // 

procedure TBaseTimer.SetInterval (value: Cardinal); 
begin 
    FInterval := value; 
end; // SetInterval // 

procedure TBaseTimer.SetResolution (value: Cardinal); 
begin 
    FResolution := value; 
end; // SetResolution // 

procedure TBaseTimer.SetOnTimer (Task: TNotifyEvent); 
begin 
    FOnTimer := Task; 
end; // SetOnTimer // 

end. // Unit: MSC_Timer_Custom // 
+0

繼承的destroy會調用WaitFor。不知道你的問題,但你不應該使用暫停或恢復。我會使用事件來暫停線程。 ProcessMessages是做什麼的? –

+2

我們不知道'繼承的Destroy'中有什麼,所以很難說。但按照慣例,不應使用「暫停」和「恢復」。最好使用同步對象(嘗試'SyncObjs.TSimpleEvent')並讓線程等待它。 –

+0

@Mason因爲我們正在調用'Terminate','Resume','Suspend','WaitFor'等等,我認爲我們可以猜測'inherited Destroy'是'TThread.Destroy'。但是你對'暫停','恢復'和事件是完全正確的。 –

回答

4

你真的不應該這樣使用Suspend()Resume()。不僅在誤用(如你)時危險,而且在D2010 +中也不推薦使用。一個更安全的替代方案是使用TEvent類代替,例如:

contructor TMIDI_Container_Publisher.Create; 
begin 
    fPublishEvent := TEvent.Create(nil, False, False, ''); 
    fTerminateEvent := TEvent.Create(nil, True, False, ''); 
    inherited Create(False); 
end; 

destructor TMIDI_Container_Publisher.Destroy; 
begin 
    Stop 
    fPublishEvent.Free; 
    fTerminateEvent.Free; 
    inherited Destroy; 
end; 

procedure TMIDI_Container_Publisher.Execute; 
var 
    h: array[0..1] of THandle; 
    ret: DWORD; 
begin 
    h[0] := fPublishEvent.Handle; 
    h[1] := fTerminateEvent.Handle; 

    while not Terminated do 
    begin 
    ret := WaitForMultipleObjects(2, h, FALSE, INFINITE); 
    if ret = WAIT_FAILED then Break; 
    case ret of 
     WAIT_OBJECT_0 + 0: FContainer.Publish; 
     WAIT_OBJECT_0 + 1: Terminate; 
    end; 
    end; 
end; 

procedure TMIDI_Container_Publisher.Publish; 
begin 
    fPublishEvent.SetEvent; 
end; 

procedure TMIDI_Container_Publisher.Stop; 
begin 
    Terminate; 
    fTerminateEvent.SetEvent; 
    WaitFor; 
end; 
+0

非常感謝你的回答!我正在尋找一種避免暫停/恢復的方式,但看起來不夠好。我將實現這個代碼,看看它是如何工作的。 – Arnold

+0

它適用於XP和7!感謝這個示例代碼,因爲這是Suspend/Resume語句的一個很好的替代品。它也是我在代碼中使用的泛型循環線程的概要。 – Arnold

3

我不知道回答你的問題,但我認爲你的代碼中有至少一個其他的bug:

我猜你有類似下面的方法:

procedure DoWork() 
begin 
    AddWork(); 
    Resume(); 
end; 

這導致競爭條件:

procedure TMIDI_Container_Publisher.Execute; 
begin 
    Suspend; 
    while not Terminated do 
    begin 
     FContainer.Publish; 
     // <= Assume code is here (1) 
     if not Terminated then { Or even worse: here (2) } Suspend; 
    end; // if 
end; // Execute // 

如果你打電話DoWork和再當它在(1)或(2)周圍的某個位置時,請求該線程立即返回暫停狀態。

如果您在執行時間爲(2)時致電Destroy,它將立即掛起,很可能永遠不會終止。

+1

它包含'Application.ProcessMessages','Suspend', '恢復'和TThread.WaitFor'。輸入法,這是四個錯誤,但我承認其他人可能會不同意。我們都知道線程掛起/恢復控制是危險的。 'TThread.WaitFor'是一個類似'Join'的關閉死鎖生成器,而A.P幾乎總是一個糟糕的設計或實際上毫無意義的指標。 –

+0

@馬丁WaitFor或加入有什麼問題?否認自己使用這些會使同步變得棘手。 –

+0

@DavidHeffernan - 只有棘手?我希望'不可能'。你可能已經猜到我更喜歡消息傳遞。我不確定哪個更大,試圖關閉的大量軟件的應用程序數量,或者性能不佳的應用程序數量,因爲它們不斷創建/銷燬線程。請注意,我不會責怪他/她的問題的OP - 德爾福的例子是在D3出來時,我懷疑他們沒有改善apalling。結果 - 數十年不理想,糟糕的設計,例如。掛起/恢復控制 - 直接從Delphi例子中。 –

2

該代碼當然存在潛在的死鎖問題。假設ExecuteDestroy併發評估not Terminated,這樣後立即從Execute走線運行,並有上下文切換:

// Thread 1      // Thread 2 
if not Terminated then 
       // context switch 
           Terminate; 
           if Suspended then Resume; 
           Application.ProcessMessages; 
           WaitFor; 
       // context switch 
    Suspend; 

現在,你在等待一個暫停的線程終止。這永遠不會取得進展。繼承的析構函數還會調用TerminateWaitFor,因此從您自己的析構函數中刪除代碼對您的程序行爲沒有太大影響也就不足爲奇了。

不要掛起線程。相反,要讓它等待一個事件,表明有更多的數據需要處理。同時,讓它等待另一個事件發出線程應該終止的信號。 (作爲該建議的擴展,不要打電話Terminate;因爲它不是虛擬的,所以它不是一個有用的終止線程的方法,它可以做任何不重要的事情。)

+0

感謝您向我指出如此清晰的競賽狀況。我將在一個示例程序中解決您的建議,這似乎是解決問題的方法。非常感謝! – Arnold

-1

嘗試暫停使用:=假,而不是簡歷。