With what delphi Code should I replace my calls to deprecated TThread method Suspend?

后端 未结 4 1362
礼貌的吻别
礼貌的吻别 2020-12-13 15:55

It has been asked before, but without a full answer. This is to do with the so called famous \"‘Fatal threading model!’\".

I need to replace this call to TThread.Su

相关标签:
4条回答
  • 2020-12-13 15:55

    Your code uses a Windows event handle, it should better be using a TEvent from the SyncObjs unit, that way all the gory details will already be taken care of.

    Also I don't understand the need for a waiting time - either your thread is blocked on the event or it isn't, there is no need for the wait operation to time out. If you do this to be able to shut the thread down - it's much better to use a second event and WaitForMultipleObjects() instead. For an example see this answer (a basic implementation of a background thread to copy files), you only need to remove the code dealing with file copying and add your own payload. You can easily implement your Start() and Stop() methods in terms of SetEvent() and ResetEvent(), and freeing the thread will properly shut it down.

    0 讨论(0)
  • 2020-12-13 16:14

    To elaborate on the original answer, (and on Smasher's rather short explanation), create a TEvent object. This is a synchronization object that's used for threads to wait on the right time to continue.

    You can think of the event object as a traffic light that's either red or green. When you create it, it's not signaled. (Red) Make sure that both your thread and the code that your thread is waiting on have a reference to the event. Then instead of saying Self.Suspend;, say EventObject.WaitFor(TIMEOUT_VALUE_HERE);.

    When the code that it's waiting on is finished running, instead of saying ThreadObject.Resume;, you write EventObject.SetEvent;. This turns the signal on (green light) and lets your thread continue.

    EDIT: Just noticed an omission above. TEvent.WaitFor is a function, not a procedure. Be sure to check it's return type and react appropriately.

    0 讨论(0)
  • 2020-12-13 16:14

    You could use an event (CreateEvent) and let the thread wait (WaitForObject) until the event is signaled (SetEvent). I know that this is a short answer, but you should be able to look these three commands up on MSDN or wherever you want. They should do the trick.

    0 讨论(0)
  • 2020-12-13 16:19

    EDIT: Latest version can be found on GitHub: https://github.com/darianmiller/d5xlib

    I've come up with this solution as a basis for TThread enhancement with a working Start/Stop mechanism that doesn't rely on Suspend/Resume. I like to have a thread manager that monitors activity and this provides some of the plumbing for that.

    unit soThread;
    
    interface
    
    uses
      Classes,
      SysUtils,
      SyncObjs,
      soProcessLock;
    
    
    type
    
      TsoThread = class;
      TsoNotifyThreadEvent = procedure(const pThread:TsoThread) of object;
      TsoExceptionEvent = procedure(pSender:TObject; pException:Exception) of object;
    
    
      TsoThreadState = (tsActive,
                        tsSuspended_NotYetStarted,
                        tsSuspended_ManuallyStopped,
                        tsSuspended_RunOnceCompleted,
                        tsTerminationPending_DestroyInProgress,
                        tsSuspendPending_StopRequestReceived,
                        tsSuspendPending_RunOnceComplete,
                        tsTerminated);
    
      TsoStartOptions = (soRepeatRun,
                         soRunThenSuspend,
                         soRunThenFree);
    
    
    
      TsoThread = class(TThread)
      private
        fThreadState:TsoThreadState;
        fOnException:TsoExceptionEvent;
        fOnRunCompletion:TsoNotifyThreadEvent;
        fStateChangeLock:TsoProcessResourceLock;
        fAbortableSleepEvent:TEvent;
        fResumeSignal:TEvent;
        fTerminateSignal:TEvent;
        fExecDoneSignal:TEvent;
        fStartOption:TsoStartOptions;
        fProgressTextToReport:String;
        fRequireCoinitialize:Boolean;
        function GetThreadState():TsoThreadState;
        procedure SuspendThread(const pReason:TsoThreadState);
        procedure Sync_CallOnRunCompletion();
        procedure DoOnRunCompletion();
        property ThreadState:TsoThreadState read GetThreadState;
        procedure CallSynchronize(Method: TThreadMethod);
      protected
        procedure Execute(); override;
    
        procedure BeforeRun(); virtual;      // Override as needed
        procedure Run(); virtual; ABSTRACT;  // Must override
        procedure AfterRun(); virtual;       // Override as needed
    
        procedure Suspending(); virtual;
        procedure Resumed(); virtual;
        function ExternalRequestToStop():Boolean; virtual;
        function ShouldTerminate():Boolean;
    
        procedure Sleep(const pSleepTimeMS:Integer);  
    
        property StartOption:TsoStartOptions read fStartOption write fStartOption;
        property RequireCoinitialize:Boolean read fRequireCoinitialize write fRequireCoinitialize;
      public
        constructor Create(); virtual;
        destructor Destroy(); override;
    
        function Start(const pStartOption:TsoStartOptions=soRepeatRun):Boolean;
        procedure Stop();  //not intended for use if StartOption is soRunThenFree
    
        function CanBeStarted():Boolean;
        function IsActive():Boolean;
    
        property OnException:TsoExceptionEvent read fOnException write fOnException;
        property OnRunCompletion:TsoNotifyThreadEvent read fOnRunCompletion write fOnRunCompletion;
      end;
    
    
    implementation
    
    uses
      ActiveX,
      Windows;
    
    
    constructor TsoThread.Create();
    begin
      inherited Create(True); //We always create suspended, user must call .Start()
      fThreadState := tsSuspended_NotYetStarted;
      fStateChangeLock := TsoProcessResourceLock.Create();
      fAbortableSleepEvent := TEvent.Create(nil, True, False, '');
      fResumeSignal := TEvent.Create(nil, True, False, '');
      fTerminateSignal := TEvent.Create(nil, True, False, '');
      fExecDoneSignal := TEvent.Create(nil, True, False, '');
    end;
    
    
    destructor TsoThread.Destroy();
    begin
      if ThreadState <> tsSuspended_NotYetStarted then
      begin
        fTerminateSignal.SetEvent();
        SuspendThread(tsTerminationPending_DestroyInProgress);
        fExecDoneSignal.WaitFor(INFINITE); //we need to wait until we are done before inherited gets called and locks up as FFinished is not yet set
      end;
      inherited;
      fAbortableSleepEvent.Free();
      fStateChangeLock.Free();
      fResumeSignal.Free();
      fTerminateSignal.Free();
      fExecDoneSignal.Free();
    end;
    
    
    procedure TsoThread.Execute();
    
                procedure WaitForResume();
                var
                  vWaitForEventHandles:array[0..1] of THandle;
                  vWaitForResponse:DWORD;
                begin
                  vWaitForEventHandles[0] := fResumeSignal.Handle;
                  vWaitForEventHandles[1] := fTerminateSignal.Handle;
                  vWaitForResponse := WaitForMultipleObjects(2, @vWaitForEventHandles[0], False, INFINITE);
                  case vWaitForResponse of
                  WAIT_OBJECT_0 + 1: Terminate;
                  WAIT_FAILED: RaiseLastOSError;
                  //else resume
                  end;
                end;
    var
      vCoInitCalled:Boolean;
    begin
      try
        try
          while not ShouldTerminate() do
          begin
            if not IsActive() then
            begin
              if ShouldTerminate() then Break;
              Suspending;
              WaitForResume();   //suspend()
    
              //Note: Only two reasons to wake up a suspended thread:
              //1: We are going to terminate it  2: we want it to restart doing work
              if ShouldTerminate() then Break;
              Resumed();
            end;
    
            if fRequireCoinitialize then
            begin
              CoInitialize(nil);
              vCoInitCalled := True;
            end;
            BeforeRun();
            try
              while IsActive() do
              begin
                Run(); //descendant's code
                DoOnRunCompletion();
    
                case fStartOption of
                soRepeatRun:
                  begin
                    //loop
                  end;
                soRunThenSuspend:
                  begin
                    SuspendThread(tsSuspendPending_RunOnceComplete);
                    Break;
                  end;
                soRunThenFree:
                  begin
                    FreeOnTerminate := True;
                    Terminate();
                    Break;
                  end;
                else
                  begin
                    raise Exception.Create('Invalid StartOption detected in Execute()');
                  end;
                end;
              end;
            finally
              AfterRun();
              if vCoInitCalled then
              begin
                CoUnInitialize();
              end;
            end;
          end; //while not ShouldTerminate()
        except
          on E:Exception do
          begin
            if Assigned(OnException) then
            begin
              OnException(self, E);
            end;
            Terminate();
          end;
        end;
      finally
        //since we have Resumed() this thread, we will wait until this event is
        //triggered before free'ing.
        fExecDoneSignal.SetEvent();
      end;
    end;
    
    
    procedure TsoThread.Suspending();
    begin
      fStateChangeLock.Lock();
      try
        if fThreadState = tsSuspendPending_StopRequestReceived then
        begin
          fThreadState := tsSuspended_ManuallyStopped;
        end
        else if fThreadState = tsSuspendPending_RunOnceComplete then
        begin
          fThreadState := tsSuspended_RunOnceCompleted;
        end;
      finally
        fStateChangeLock.Unlock();
      end;
    end;
    
    
    procedure TsoThread.Resumed();
    begin
      fAbortableSleepEvent.ResetEvent();
      fResumeSignal.ResetEvent();
    end;
    
    
    function TsoThread.ExternalRequestToStop:Boolean;
    begin
      //Intended to be overriden - for descendant's use as needed
      Result := False;
    end;
    
    
    procedure TsoThread.BeforeRun();
    begin
      //Intended to be overriden - for descendant's use as needed
    end;
    
    
    procedure TsoThread.AfterRun();
    begin
      //Intended to be overriden - for descendant's use as needed
    end;
    
    
    function TsoThread.Start(const pStartOption:TsoStartOptions=soRepeatRun):Boolean;
    var
      vNeedToWakeFromSuspendedCreationState:Boolean;
    begin
      vNeedToWakeFromSuspendedCreationState := False;
    
      fStateChangeLock.Lock();
      try
        StartOption := pStartOption;
    
        Result := CanBeStarted();
        if Result then
        begin
          if (fThreadState = tsSuspended_NotYetStarted) then
          begin
            //Resumed() will normally be called in the Exec loop but since we
            //haven't started yet, we need to do it here the first time only.
            Resumed();
            vNeedToWakeFromSuspendedCreationState := True;
          end;
    
          fThreadState := tsActive;
    
          //Resume();
          if vNeedToWakeFromSuspendedCreationState then
          begin
            //We haven't started Exec loop at all yet
            //Since we start all threads in suspended state, we need one initial Resume()
            Resume();
          end
          else
          begin
            //we're waiting on Exec, wake up and continue processing
            fResumeSignal.SetEvent();
          end;
        end;
      finally
        fStateChangeLock.Unlock();
      end;
    end;
    
    
    procedure TsoThread.Stop();
    begin
      SuspendThread(tsSuspendPending_StopRequestReceived);
    end;
    
    
    procedure TsoThread.SuspendThread(const pReason:TsoThreadState);
    begin
      fStateChangeLock.Lock();
      try
        fThreadState := pReason; //will auto-suspend thread in Exec
        fAbortableSleepEvent.SetEvent();
      finally
        fStateChangeLock.Unlock();
      end;
    end;
    
    
    procedure TsoThread.Sync_CallOnRunCompletion();
    begin
      if Assigned(fOnRunCompletion) then fOnRunCompletion(Self);
    end;
    
    
    procedure TsoThread.DoOnRunCompletion();
    begin
      if Assigned(fOnRunCompletion) then CallSynchronize(Sync_CallOnRunCompletion);
    end;
    
    
    function TsoThread.GetThreadState():TsoThreadState;
    begin
      fStateChangeLock.Lock();
      try
        if Terminated then
        begin
          fThreadState := tsTerminated;
        end
        else if ExternalRequestToStop() then
        begin
          fThreadState := tsSuspendPending_StopRequestReceived;
        end;
        Result := fThreadState;
      finally
        fStateChangeLock.Unlock();
      end;
    end;
    
    
    function TsoThread.CanBeStarted():Boolean;
    begin
      Result := (ThreadState in [tsSuspended_NotYetStarted,
                                 tsSuspended_ManuallyStopped,
                                 tsSuspended_RunOnceCompleted]);
    end;
    
    function TsoThread.IsActive():Boolean;
    begin
      Result := (ThreadState = tsActive);
    end;
    
    
    procedure TsoThread.Sleep(const pSleepTimeMS:Integer);
    begin
      fAbortableSleepEvent.WaitFor(pSleepTimeMS);
    end;
    
    
    procedure TsoThread.CallSynchronize(Method: TThreadMethod);
    begin
      if IsActive() then
      begin
        Synchronize(Method);
      end;
    end;
    
    Function TsoThread.ShouldTerminate():Boolean;
    begin
      Result := Terminated or
                (ThreadState in [tsTerminationPending_DestroyInProgress, tsTerminated]);
    end;
    
    end.
    
    0 讨论(0)
提交回复
热议问题