Resuming suspended thread in Delphi 2010?
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;interfaceuses 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;implementationconstructor 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;beginend;procedure TDecoupledThread.DoSetUp;beginend;procedure TDecoupledThread.DoTearDown;beginend;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;interfaceuses 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;interfaceuses 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;implementationconstructor 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);beginend;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
Short answer: call inherited Create(false) and omitt the Start!
The actual Start of a non-create-suspended thread is done in AfterConstruction, which is called after all constructors have been called.