Search code examples
multithreadingdelphidelphi-2010

Resuming suspended thread in Delphi 2010?


TThread's resume method is deprecated in D2010. So, I thought it should now work like this:

TMyThread = class (TThread)
protected
  Execute; override;
public
  constructor Create;
end;
...

TMyThread.Create;
begin
  inherited Create (True);
  ...
  Start;
 end;

Unfortunately I get an exception "Cannot call start on a running or supsended thread"...which seems weird to me considering the fact that the documentation tells me that I should call Start on a thread created in suspended mode.

What am I missing here?


Solution

  • The reason is that a Thread is not supposed to start itself.

    The thread never knows when initialization is complete. Construction is not the same as initialization (construction should always be short and exception free; further initialization is done after construction).

    A similar situation is a TDataSet: no TDataSet constructor should ever call Open, or set Active := True.

    See also this blog entry by Wings of Wind.

    You should either:

    • Create the TMyThread suspended by calling Create(true) and perform the Start outside your TMyThread class
    • Create the TMyThread non-suspeneded, making sure the Create constructor does full initialization, and let TThread.AfterConstruction start the thread.

    Explanation of TThread usage:

    Basically, a thread should be just that: the encapsulation of the context on which code is executed.

    The actual code (the business logic) that is executed should then be in other classes.

    By decoupling those two, you gain a lot of flexibility, especially initiating your business logic from within multiple places (which is very convenient when writing unit tests!).

    This is the kind of framework you could use for that:

    unit DecoupledThreadUnit;
    
    interface
    
    uses
      Classes;
    
    type
      TDecoupledThread = class(TThread)
      strict protected
        //1 called in the context of the thread
        procedure DoExecute; virtual;
        //1 Called in the context of the creating thread (before context of the new thread actualy lives)
        procedure DoSetUp; virtual;
        //1 called in the context of the thread right after OnTerminate, but before the thread actually dies
        procedure DoTearDown; virtual;
      protected
        procedure DoTerminate; override;
        procedure Execute; override;
      public
        constructor Create;
        procedure AfterConstruction; override;
      end;
    
    implementation
    
    constructor TDecoupledThread.Create;
    begin
      // create suspended, so that AfterConstruction can call DoSetup();
      inherited Create(True);
    end;
    
    procedure TDecoupledThread.AfterConstruction;
    begin
      // DoSetUp() needs to be called without the new thread in suspended state
      DoSetUp();
      // this will unsuspend the underlying thread
      inherited AfterConstruction;
    end;
    
    procedure TDecoupledThread.DoExecute;
    begin
    end;
    
    procedure TDecoupledThread.DoSetUp;
    begin
    end;
    
    procedure TDecoupledThread.DoTearDown;
    begin
    end;
    
    procedure TDecoupledThread.DoTerminate;
    begin
      inherited DoTerminate();
      // call DoTearDown on in the thread context right before it dies:
      DoTearDown();
    end;
    
    procedure TDecoupledThread.Execute;
    begin
      // call DoExecute on in the thread context
      DoExecute();
    end;
    
    end.
    

    You could even make it event based by something like this:

    unit EventedThreadUnit;
    
    interface
    
    uses
      Classes,
      DecoupledThreadUnit;
    
    type
      TCustomEventedThread = class(TDecoupledThread)
      private
        FOnExecute: TNotifyEvent;
        FOnSetUp: TNotifyEvent;
        FOnTearDown: TNotifyEvent;
      strict protected
        procedure DoExecute; override;
        procedure DoSetUp; override;
        procedure DoTearDown; override;
      public
        property OnExecute: TNotifyEvent read FOnExecute write FOnExecute;
        property OnSetUp: TNotifyEvent read FOnSetUp write FOnSetUp;
        property OnTearDown: TNotifyEvent read FOnTearDown write FOnTearDown;
      end;
    
      // in case you want to use RTTI
      TEventedThread = class(TCustomEventedThread)
      published
        property OnExecute;
        property OnSetUp;
        property OnTearDown;
      end;
    
    implementation
    
    { TCustomEventedThread }
    
    procedure TCustomEventedThread.DoExecute;
    var
      TheOnExecute: TNotifyEvent;
    begin
      inherited;
      TheOnExecute := OnExecute;
      if Assigned(TheOnExecute) then
        TheOnExecute(Self);
    end;
    
    procedure TCustomEventedThread.DoSetUp;
    var
      TheOnSetUp: TNotifyEvent;
    begin
      inherited;
      TheOnSetUp := OnSetUp;
      if Assigned(TheOnSetUp) then
        TheOnSetUp(Self);
    end;
    
    procedure TCustomEventedThread.DoTearDown;
    var
      TheOnTearDown: TNotifyEvent;
    begin
      inherited;
      TheOnTearDown := OnTearDown;
      if Assigned(TheOnTearDown) then
        TheOnTearDown(Self);
    end;
    
    end.
    

    Or adapt it for DUnit TTestCase descendants like this:

    unit TestCaseThreadUnit;
    
    interface
    
    uses
      DecoupledThreadUnit,
      TestFramework;
    
    type
      TTestCaseRanEvent = procedure (Sender: TObject; const TestResult: TTestResult) of object;
      TTestCaseThread = class(TDecoupledThread)
      strict private
        FTestCase: TTestCase;
      strict protected
        procedure DoTestCaseRan(const TestResult: TTestResult); virtual;
        function GetTestCase: TTestCase; virtual;
        procedure SetTestCase(const Value: TTestCase); virtual;
      protected
        procedure DoExecute; override;
        procedure DoSetUp; override;
        procedure DoTearDown; override;
      public
        constructor Create(const TestCase: TTestCase);
        property TestCase: TTestCase read GetTestCase write SetTestCase;
      end;
    
    implementation
    
    constructor TTestCaseThread.Create(const TestCase: TTestCase);
    begin
      inherited Create();
      Self.TestCase := TestCase;
    end;
    
    procedure TTestCaseThread.DoExecute;
    var
      TestResult: TTestResult;
    begin
      if Assigned(TestCase) then
      begin
        // this will call SetUp and TearDown on the TestCase
        TestResult := TestCase.Run();
        try
          DoTestCaseRan(TestResult);
        finally
          TestResult.Free;
        end;
      end
      else
        inherited DoExecute();
    end;
    
    procedure TTestCaseThread.DoTestCaseRan(const TestResult: TTestResult);
    begin
    end;
    
    function TTestCaseThread.GetTestCase: TTestCase;
    begin
      Result := FTestCase;
    end;
    
    procedure TTestCaseThread.SetTestCase(const Value: TTestCase);
    begin
      FTestCase := Value;
    end;
    
    procedure TTestCaseThread.DoSetUp;
    begin
      if not Assigned(TestCase) then
        inherited DoSetUp();
    end;
    
    procedure TTestCaseThread.DoTearDown;
    begin
      if not Assigned(TestCase) then
        inherited DoTearDown();
    end;
    
    end.
    

    --jeroen