Suppose I have following codes
Unit TalkerIntf.pas
unit TalkerIntf;
interface
{$MODE OBJFPC}
type
ITalker = interface
['{95E1FAE3-7495-4404-AE88-6A7DB88383EC}']
procedure say() ;
end;
implementation
end.
Unit TalkerImpl.pas
unit TalkerImpl;
interface
{$MODE OBJFPC}
uses
TalkerIntf;
type
TTalker = class(TInterfacedObject, ITalker)
public
procedure say();
end;
implementation
procedure TTalker.say();
begin
writeln('Hello');
end;
end.
Unit DelegateTalkerImpl.pas
unit DelegateTalkerImpl;
interface
{$MODE OBJFPC}
uses
TalkerIntf;
type
TDelegateTalker = class(TInterfacedObject, ITalker)
private
fActualTalker : ITalker;
public
constructor create(const talker : ITalker);
destructor destroy(); override;
property talker : ITalker read fActualTalker implements ITalker;
end;
implementation
constructor TDelegateTalker.create(const talker : ITalker);
begin
fActualTalker := talker;
end;
destructor TDelegateTalker.destroy();
begin
fActualTalker := nil;
inherited destroy();
end;
end.
and program memleak.pas
program memleak;
{$MODE OBJFPC}
uses
TalkerIntf,
TalkerImpl,
DelegateTalkerImpl;
var
talker : ITalker;
begin
talker := TDelegateTalker.create(TTalker.create());
talker.say();
end.
Compiling with FreePascal 3.0.4 and heaptrc on (-gh), heaptrc reports there are memory leak
$ fpc -gh memleak.pas
$ ./memleak
Heaptrc output
Hello
Heap dump by heaptrc unit
2 memory blocks allocated : 64/64
0 memory blocks freed : 0/0
2 unfreed memory blocks : 64
True heap size : 32768
True free heap : 32384
Should be : 32448
Call trace for block $00007FA0D7846180 size 32
$000000000040020F
Call trace for block $00007FA0D78460C0 size 32
Why is this interface delegation causing memory leak? How to avoid it?
Original post
Update
It seems the only workaround is remove implements and do delegation manually. Following code does not suffer from memory leak.
unit DelegateTalkerImpl;
interface
{$MODE OBJFPC}
uses
TalkerIntf;
type
TDelegateTalker = class(TInterfacedObject, ITalker)
private
fActualTalker : ITalker;
public
constructor create(const talker : ITalker);
destructor destroy(); override;
procedure say();
end;
implementation
constructor TDelegateTalker.create(const talker : ITalker);
begin
fActualTalker := talker;
end;
destructor TDelegateTalker.destroy();
begin
fActualTalker := nil;
inherited destroy();
end;
procedure TDelegateTalker.say();
begin
fActualTalker.say();
end;
end.
According to ASerge's comment
When you define a variable as ITalker, the newly created object is not returned, but only the field that implements it. As a result, a newly created object is leaked.
Delphi's behavior is the same.
To avoid memory leak but still using implements keyword, we need to assign to temporary variable of type other than ITalker and then typecast it to ITalker.
var
talker : ITalker;
delegateTalker : IInterface;
begin
delegateTalker := TDelegateTalker.create(TTalker.create());
talker := delegateTalker as ITalker;
talker.say();
end.
Related
I want to implement a singleton critical section in order to protect some code across several classes(don't ask further...). I've seen Delphi Singleton Pattern and Aquire Singleton class Instance Multithread but these are using a global function to return the singleton and I don't want that. So, I've implemented my own version:
unit SynchronizationHandler;
interface
uses
SyncObjs;
type
TSynchronizationHandler = class
strict private
FCriticalSection: TCriticalSection;
private
class var FSingletonCriticalSection: TCriticalSection;
class var FInstance: TSynchronizationHandler;
public
constructor Create;
destructor Destroy; override;
procedure Lock;
procedure Release;
class function Instance: TSynchronizationHandler;
end;
implementation
{ TSynchronizationHandler }
constructor TSynchronizationHandler.Create;
begin
FCriticalSection := TCriticalSection.Create;
end;
destructor TSynchronizationHandler.Destroy;
begin
FCriticalSection.Destroy;
end;
{Doublec check locking for this singletone}
class function TSynchronizationHandler.Instance: TSynchronizationHandler;
begin
if not Assigned(FInstance) then
begin
FSingletonCriticalSection.Acquire;
try
if not Assigned(FInstance) then
FInstance := TSynchronizationHandler.Create;
finally
FSingletonCriticalSection.Release;
end;
end;
Result := FInstance;
end;
procedure TSynchronizationHandler.Lock;
begin
FCriticalSection.Acquire;
end;
procedure TSynchronizationHandler.Release;
begin
FCriticalSection.Release;
end;
initialization
TSynchronizationHandler.FSingletonCriticalSection := TCriticalSection.Create;
finalization
if Assigned(TSynchronizationHandler.FInstance) then
TSynchronizationHandler.Instance.Free;
TSynchronizationHandler.FSingletonCriticalSection.Free;
end.
It's working but I don't like the initialization\finalization part. Is there any other way to accomplish this without using global variables, functions?
If you want to encapsulate all the necessary code inside the namespace of the class then you can replace the initialization and finalization sections with a class constructor and a class destructor, respectively. These will be called implicitly from the unit initialization and finalization procedures.
I wrote a little program that shows my problem:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
TThread_A = class(TThread)
private
stupidvariable : integer;
protected
procedure Execute; override;
public
property getstupidvar : integer read stupidvariable;
constructor Create;
end;
TSomeClass = class
private
m_Obj : ^TThread_A;
procedure readVar;
public
constructor Create(obj: TThread_A);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TSomeClass.Create(obj: TThread_A);
begin
m_Obj := #obj;
readVar;
end;
procedure TSomeClass.readVar;
begin
showmessage(inttostr(m_Obj.getstupidvar));
end;
constructor TThread_A.Create;
begin
inherited Create(false);
FreeOnTerminate := True;
end;
procedure TThread_A.Execute;
begin
stupidvariable := 100;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
threadA : TThread_A;
someClass : TSomeClass;
begin
threadA := TThread_A.Create;
someClass := TSomeClass.Create(threadA);
end;
end.
What happens here exactly?
I thought I am passing the object "threadA" to someClass and assign the address of "threadA" to "m_Obj".
Why is the object lost?
constructor TSomeClass.Create(obj: TThread_A);
begin
m_Obj := #obj;
readVar;
end;
Here obj is (in essence) a local variable, and so its lifetime ends when the function returns. Therefore you have remembered the address of something that no longer exists.
In fact you have too much indirection. Because TThread_A is a class, and classes are reference types, it is already a pointer. Change
m_Obj: ^TThread_A;
to
m_Obj: TThread_A;
and
m_Obj := #obj;
to
m_Obj := obj;
Now you are taking a copy of the reference to the instance, which is what I believe that you mean to do.
Not even that will leave you with a working program though. Because you set FreeOnTerminate the thread can be destroyed at any time. That means that you must not hold a reference to it since that reference can become invalid behind your back. So, you should also set FreeOnTerminate to False.
Not even that will leave you with predicatable outcome though. The thread procedure executes independently from the main thread. When you read the variable from the main thread, the thread may, or may not, have modified the variable. This is known as a data race. If you wish to wait until after the variable has been modified then you could use, for instance, an event object to allow the thread to signal that the variable is ready to be read.
I'm trying to write a simple CPort application in delphi.
I want it to listen to a port, upon receiving a message, it will wait 4 seconds then send a string in response.
unit Tests.Mocks.Refractometer;
interface
uses
CPort,
Classes
;
type
TRefractometerMock = class
strict private
MockRunThread : TThread;
ComPort : TComPort;
ComDataPacket: TComDataPacket;
public
procedure Open;
procedure HandlePacket(Sender : TObject; const Str : String);
constructor Create; overload;
constructor Create(BaudRate : TBaudRate; Port : String); overload;
destructor Destroy; override;
end;
implementation
uses
SysUtils,
StrUtils
;
procedure TRefractometerMock.HandlePacket(Sender : TObject; const Str : String);
begin
MockRunThread.Start;
end;
procedure TRefractometerMock.Open;
begin
ComPort.Open;
end;
constructor TRefractometerMock.Create(BaudRate : TBaudRate; Port : String);
begin
Self.Create;
Self.ComPort.Port := Port;
Self.ComPort.BaudRate := BaudRate;
end;
constructor TRefractometerMock.Create;
begin
inherited;
ComPort := TComPort.Create(nil);
ComDataPacket := TComDataPacket.Create(nil);
ComDataPacket.ComPort := ComPort;
ComDataPacket.OnPacket := HandlePacket;
MockRunThread := TThread.CreateAnonymousThread
(
procedure
begin
Sleep(4000);
Self.ComPort.WriteStr('nD=1.33308;');
end
);
end;
destructor TRefractometerMock.Destroy;
begin
if Assigned(Self.ComPort) then FreeAndNil(Self.ComPort);
if Assigned(ComDataPacket) then FreeAndNil(ComDataPacket);
if Assigned(MockRunThread) then FreeAndNil(MockRunThread);
inherited;
end;
end.
using this unit I can use the following code to
Start listening
RefractometerMock := TRefractometerMock.Create(TBaudRate.br9600, 'COM7');
try
RefractometerMock.Open;
Sleep(8000);
finally
FreeAndNil(RefractometerMock);
end;
Also note that I'm using com0com to create a bridge between ports COM6 and COM7.
I'm sending a putty message on port COM6
The problem is that even though I have sent a message with putty, the HandlePacket method does not get called until the TRefractometerMock object is freed.
First
Then
Then
Finally
I'm not even sure how this is possible since I thought this object had been destroyed.
You are blocking the main thread by Sleep(8000). This means that the com port driver is not able to call the HandlePacket method.
When the sleep is over, it is too late to handle anything, since everything is freed.
Since you are handling the life time of the anonymous thread, you should set the FreeOnTerminate property to false. And free the com port after the anonymous thread.
Use a timer instead of the Sleep() call.
I don't see any settings for ComDataPacket. From help: Packet ends when one of stop conditions occurs. Did you define these stop conditions?
Have you checked data receiving using ComDataPacket in simple standalone application, without intermediate class?
BTW, it seems that TTimer could do the work, thread is not necessary here.
Since Brian Frost wants the code here it is, this is too big to fit in a comment.
Create a simple form
type
TFrmMockRefrac = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
RefractometerMock : TRefractometerMock;
{ Public declarations }
end;
var
FrmMockRefrac: TFrmMockRefrac;
Handle creating and destruction without the Sleep function
procedure TFrmMockRefrac.FormCreate(Sender: TObject);
begin
RefractometerMock := TRefractometerMock.Create(TBaudRate.br9600, 'COM7');
RefractometerMock.Open;
end;
procedure TFrmMockRefrac.FormDestroy(Sender: TObject);
begin
FreeAndNil(RefractometerMock);
end;
I am implementing a pool of objects in Delphi. I need to synchronize the threads to get the objects from the pool.
Thread Code:
uClientQueryPool.CLIENT_POOL_GUARD.Acquire();
QueryClient := QUERY_POOL.GetClient();
uClientQueryPool.CLIENT_POOL_GUARD.Release;
Pool Code:
var
CLIENT_POOL_GUARD: TCriticalSection;
type
TClientQueryPool = class
public
function GetClient(): TQueryClient;
end;
The CLIENT_POOL_GUARD is a unit variable. The pool is working well, but can I use "uClientQueryPool.CLIENT_POOL_GUARD.Acquire();" and "uClientQueryPool.CLIENT_POOL_GUARD.Release;" inside the GetClient method?
Like this:
function TClientQueryPool.GetClient: TQueryClient;
begin
CLIENT_POOL_GUARD.Acquire();
...
CLIENT_POOL_GUARD.Release;
end;
Moving the lock inside the get/pop/whatever method is just fine, as is making the CriticalSection instance a private member of the pool class. Use the same CS in the release() call that pushes the objects back onto the pool.
Been doing this for decades, usually with TObjectQueue as the pool queue, a CS to protect it and a semaphore to count the pool contents and something for requesting threads to block on if the pool empties temporarily.
Don't know where that 'double acquire' thread came from. Either the lock is inside the pool class, or outside. I really can't imagine why anyone would code up both!
Example classes:
First, thread-safe P-C queue, for holding the pooled objects:
unit tinySemaphoreQueue;
interface
uses
Windows, Messages, SysUtils, Classes,syncObjs,contnrs;
type
pObject=^Tobject;
TsemaphoreMailbox=class(TobjectQueue)
private
countSema:Thandle;
protected
access:TcriticalSection;
public
property semaHandle:Thandle read countSema;
constructor create; virtual;
procedure push(aObject:Tobject); virtual;
function pop(pResObject:pObject;timeout:DWORD):boolean; virtual;
end;
implementation
{ TsemaphoreMailbox }
constructor TsemaphoreMailbox.create;
begin
inherited Create;
access:=TcriticalSection.create;
countSema:=createSemaphore(nil,0,maxInt,nil);
end;
function TsemaphoreMailbox.pop(pResObject: pObject;
timeout: DWORD): boolean;
begin // wait for a unit from the semaphore
result:=(WAIT_OBJECT_0=waitForSingleObject(countSema,timeout));
if result then // if a unit was supplied before the timeout,
begin
access.acquire;
try
pResObject^:=inherited pop; // get an object from the queue
finally
access.release;
end;
end;
end;
procedure TsemaphoreMailbox.push(aObject: Tobject);
begin
access.acquire;
try
inherited push(aObject); // shove the object onto the queue
finally
access.release;
end;
releaseSemaphore(countSema,1,nil); // release one unit to semaphore
end;
end.
then object pool:
unit tinyObjectPool;
interface
uses
Windows, Messages, SysUtils, Classes,syncObjs,contnrs,
tinySemaphoreQueue;
type
TobjectPool=class;
TpooledObject=class(TObject)
private
FmyPool:TObjectPool;
protected
Fparameter:TObject;
public
procedure release;
constructor create(parameter:TObject); virtual;
end;
TpooledObjectClass=class of TpooledObject;
TobjectPool=class(TsemaphoreMailbox)
private
Fparameter:TObject;
function getPoolLevel: integer;
public
property poolLevel:integer read getPoolLevel;
constructor create(poolDepth:integer;
pooledObjectClass:TpooledObjectClass;parameter:TObject); reintroduce; virtual;
end;
implementation
{ TobjectPool }
constructor TobjectPool.create(poolDepth: integer;
pooledObjectClass: TpooledObjectClass;parameter:TObject);
var objectCount:integer;
thisObject:TpooledObject;
begin
inherited create;
Fparameter:=parameter; // a user parameter passed to all objects
for objectCount:=0 to poolDepth-1 do // fill up the pool with objects
begin
thisObject:=pooledObjectClass.create(parameter);
thisObject.FmyPool:=self;
inherited push(thisObject);
end;
end;
function TobjectPool.getPoolLevel: integer;
begin
access.acquire;
result:=inherited count;
access.release;
end;
{ TpooledObject }
constructor TpooledObject.create(parameter: TObject);
begin
inherited create;
Fparameter:=parameter;
end;
procedure TpooledObject.release;
begin
FmyPool.push(self);
end;
end.
Yes you can. Note, though that although you can pull an object from the pool in a thread-safe manner, it may not be thread-safe to use it if the object itself isn't thread-safe. For instance, in the example below, the pool is thread safe and even makes threads wait if all objects in the pool are in use, but once an object is in use, using it still is not thread safe, because it uses global data.
uses
SyncObjs;
var
GlobalData: Integer = 0;
type
TDataObject = class
Used: Boolean;
procedure UpdateData;
end;
type
TPool = class
FLock: TCriticalSection;
FSemaphore: TSemaphore;
FDataObjects: array[0..9] of TDataObject;
constructor Create;
destructor Destroy; override;
function GetDataObject: TDataObject;
procedure ReleaseDataObject(AObject: TDataObject);
end;
var
Pool: TPool;
type
TDataThread = class(TThread)
constructor Create;
procedure Execute; override;
end;
{ TPool }
constructor TPool.Create;
var
i: Integer;
begin
inherited Create;
FLock := TCriticalSection.Create;
FSemaphore := TSemaphore.Create(nil, Length(FDataObjects), Length(FDataObjects), '', False);
for i := Low(FDataObjects) to High(FDataObjects) do
FDataObjects[i] := TDataObject.Create;
end;
destructor TPool.Destroy;
var
i: Integer;
begin
for i := Low(FDataObjects) to High(FDataObjects) do
FDataObjects[i].Free;
FSemaphore.Free;
FLock.Free;
end;
function TPool.GetDataObject: TDataObject;
var
i: Integer;
begin
Result := nil;
FLock.Acquire;
try
FSemaphore.Acquire;
for i := Low(FDataObjects) to High(FDataObjects) do
if not FDataObjects[i].Used then
begin
Result := FDataObjects[i];
Result.Used := True;
Exit;
end;
Assert(Result <> nil, 'Pool did not return an object');
finally
FLock.Release;
end;
end;
procedure TPool.ReleaseDataObject(AObject: TDataObject);
begin
if not AObject.Used then
raise Exception.Create('Data object cannot be released, because it is not in use.');
AObject.Used := False;
FSemaphore.Release;
end;
{ TDataObject }
procedure TDataObject.UpdateData;
begin
Inc(GlobalData);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TDataThread.Create;
end;
{ TDataThread }
constructor TDataThread.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
Resume;
end;
procedure TDataThread.Execute;
var
DataObject: TDataObject;
begin
DataObject := Pool.GetDataObject;
DataObject.UpdateData; // <-- Not thread-safe!
Pool.ReleaseDataObject(DataObject);
end;
initialization
Pool := TPool.Create;
finalization
Pool.Free;
end.
1) I'd remove Acquire/Release code from threads code - it is fragile. In one thread you forget to call it - and ba-bang! Security measures, as a rule of thumb, should be centralized and enforced by server, not distributed in fuzzy way in clients.
2) Acquire/Release calls should be guarded from errors, else any stray exception would forever lock all the threads.
function TClientQueryPool.GetClient: TQueryClient;
begin
CS.Acquire;
try
// actually getting object, preferably just calling
// internal non-public thread-unsafe method for it
finally
CS.Release;
end;
end;
3) Critical section itself should better be a Pool's internal, non-public member. That way you would be allowed in future, when you forget of implementation details, easy refactoring, like:
3.1) implementing several pools
3.2) moving pool code to another unit
3.3) ensuring any stray erroneous code outside pool would not be able to crash the application be randomly acquiring or releasing the CS
4) Double calling of acquire/release over TCriticalSection object puts all your bets over implications from a single note in TCriticalSection documentation, pointed to by The_Fox.
"Each call to Release should be balance by an earlier call to Acquire"
http://docwiki.embarcadero.com/Libraries/en/System.SyncObjs.TCriticalSection.Release
And over the hope that all other Pascal implementations today and tomorrow would not miss it.
That is fragile practice. And multi-threading code is famous for creating Heisenbugs, when there are problems at clients sites, but you can not reproduce and find it in house.
If in future your company would expand to different platform or different language implementation, that puts a potential land mine. And the kind of mine, that would be hard to find by testing in house. Multithreading code is the place where you'd better be over-defeinsive and just do not allow ANY uncertainty to happen.
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?
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
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.