2011-10-31 3 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 // 

이 파괴는 Windows 7에서 정상적으로 작동하지만 XP에서는 중단됩니다. 문제는 WaitFor 것 같습니다 있지만이 제거 할 때 inherited Destroy 코드가 중단됩니다.

아무도 아이디어가 잘못 되었나요?


업데이트 2011/11/02 모두에게 감사드립니다. Remy Labeau는 Resume/Suspend를 피하기 위해 코드 예제를 제공했습니다. 지금부터 내 프로그램에서 그의 제안을 구현하겠습니다. 이 특별한 경우에 나는 CodeInChaos의 제안에 영감을 받았다. 스레드를 생성하고 실행에서 게시하고 잊어 버리십시오. 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

우리는'상속 된 파괴 '에 무엇이 있는지 모릅니다. 그래서 말하기 어렵습니다. 그러나 일반적으로'Suspend'와'Resume'을 사용해서는 안됩니다. 동기화 객체를 사용하고 (SyncObjs.TSimpleEvent'를 시도하십시오) 스레드가 기다려야합니다. –

+0

@Mason 우리는'Terminate','Resume','Suspend','WaitFor' 등을 호출하기 때문에'상속 된 Destroy'가'TThread.Destroy'라고 생각할 수 있습니다. 그러나 당신은'Suspend','Resume' 및 이벤트에 대해 옳습니다. –

답변

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

나는 당신의 질문에 대한 답을 모르는,하지만 난 당신의 코드는 적어도 하나의 다른 버그가 있다고 생각 :

난 당신이 다음과 같은 방법이 생각 :

procedure DoWork() 
begin 
    AddWork(); 
    Resume(); 
end; 
을 당신이 DoWork 다시 호출하면

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 // 

:

이 경쟁 조건에 이르게 쓰레드가 멈추거나 (1) 또는 (2) 어딘가에있는 동안 쓰레드가 즉시 정지 상태로 돌아갑니다.

실행이 약 (2) 인 동안 Destroy으로 전화하면 즉시 일시 중단되며 거의 종료되지 않습니다.

+1

'Application.ProcessMessages', 'Suspend', 'Resume'과 TThread. 기다려. ' IME, 네 가지 버그가 있습니다. 다른 사람들이 동의하지 않을 수도 있음을 알고 있습니다. 우리는 스레드의 일시 중단/다시 시작 제어가 위험하다는 것을 모두 알고 있습니다. 'TThread.WaitFor'는 'Join'과 같은 종료 및 교착 상태 생성기이며 A.P는 거의 항상 잘못된 디자인의 지표이거나 실제로 무의미합니다. –

+0

@martin WaitFor 또는 join에 무슨 문제가 있습니까? 그것들을 사용하는 것을 거부하면 동기화가 까다로워집니다. –

+0

@DavidHeffernan - 까다 롭습니까? 나는 '불가능'하기를 바랐다. 당신은 내가 메시지 전달을 선호한다고 짐작할 수 있습니다. 어떤 스레드가 계속해서 만들어 지거나 파괴되기 때문에 성능이 떨어지는 응용 프로그램의 수나 종료하려고 시도 할 때 대용량의 대용량 보드가있는 응용 프로그램의 개수가 더 많은지 확실하지 않습니다. 그/그녀의 문제에 대한 OP를 비난하지 않는다는 점에 유의하십시오. 델파이 예제는 D3이 나왔을 때 좋았습니다. 그리고 개선되지 않았다고 생각합니다. 결과 - 차선의, 열악한 디자인의 수십 년. 일시 중지/다시 시작 제어 - 곧장 델파이 예제에서. –

2

확실히 코드에서 교착 상태 가능성이 있습니다. 바로이 같은 not Terminated을 평가 한 후 거리 Execute 스레드에서 스위치 가정 ExecuteDestroy 동시에 실행하고 문맥이있다 :

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

지금 당신이 중단 된 thread의 종료를 기다리고 있습니다. 그것은 결코 진전을 보이지 않을 것입니다. 상속받은 소멸자는 TerminateWaitFor을 호출하기 때문에 소멸자의 코드를 제거한다고해서 프로그램의 동작에 큰 영향을주지는 않습니다.

스레드를 일시 중단하지 마십시오. 대신 처리 할 데이터가 더 있음을 알리는 이벤트를 기다립니다. 동시에 스레드가 종료되어야한다는 신호를 다른 이벤트가 기다릴 수있게하십시오. (그 충고의 연장선에서, Terminate을 호출하는 것을 괴롭히지 말라. 가상이 아니기 때문에, 사소한 일을하는 스레드를 종료하는 데는 유용한 방법이 아니다.)

+0

내게 경쟁 조건을 분명히 지적 해 주셔서 감사합니다. 샘플 프로그램에서 조언을 구할 것입니다. 문제의 해결 방법 인 것 같습니다. 고마워요! – Arnold

-1

시도의 사용 중지 : 대신 이력서 = false입니다.

관련 문제