I have multithreaded app and I have a question regarding assigning objects between threads and how to lock them properly.
I defined custom type class and in main thread I create an instance of that type. I would like to assign different objects to a thread, those objects will be used within Execute method of a thread.
type TMyClass = class
private
FData: Integer;
public
property Data: Integer read FData write FData;
end;
TMyThread = class(TThread)
private
FMyObject: TMyObject;
FLock: TCriticalSection;
protected
procedure Execute; override;
public
procedure Lock;
procedure Unlock;
property MyObject: TMyObject read FMyObject write FMyObject;
end;
procedure TMyThread.Lock;
begin
FLock.Acquire;
end;
procedure TMyThread.Unlock;
begin
FLock.Release;
end;
procedure TMyThread.Execute;
begin
while not Terminated do
begin
Lock;
try
if Assigned(FMyObject) then
FMyObject.Data := FMyObject.Data + 1;
finally
Unlock;
end;
end;
end;
from main thread:
var MyObject1, MyObject2: TMyObject;
thOperation: TMyThread;
CurrData1, CurrData2: Integer;
begin
// create two objects
MyObject1 := TMyObject.Create;
MyObject2 := TMyObject.Create;
// create thread(started)
thOperation := TMyThread.Create(false);
thOperation.Lock;
try
thOperation.MyObject := MyObject1;
finally
thOperation.Unlock;
end;
/// .... do some stuff in main thread
thOperation.Lock;
try
CurrData1 := thOperation.MyObject.Data;
finally
Unlock;
end;
// let's assign new object on a running thread
thOperation.Lock;
try
thOperation.MyObject := MyObject2;
finally
thOperation.Unlock;
end;
/// .... do some stuff in main thread again
thOperation.Lock;
try
CurrData2 := thOperation.MyObject.Data;
finally
Unlock;
end;
if CurrData1 <> CurrData2 then ShowMessage('Different result!');
// do cleanup
thOperation.Terminate;
thOperation.WaitFor;
thOperation.Free;
MyObject1.Free;
MyObject2.Free;
end;
Is this approach of locking when assigning different objects to a thread ok?
To answer your question, yes, your approach of using TCriticalSection is ok.
For more information on multithreading, in case you don't have it yet, Google for 'Multithreading - The Delphi way' by Martin Harvey. An excellent article (or should I say book).
Related
Hi I'm doing a code MessageDlgPos running five threads at the same time, the code is this:
type
TMyThread = class(TThread)
protected
procedure Execute; override;
public
text: string;
property ReturnValue;
end;
procedure TMyThread.Execute;
begin
if Terminated then
Exit;
MessageDlgPos(text, mtInformation, [mbOk], 0, 100, 200);
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
LThread: TMyThread;
i: Integer;
begin
For i := 1 to 5 do
begin
LThread := TMyThread(Sender);
try
LThread.text := 'hi';
LThread.FreeOnTerminate := True;
except
LThread.Free;
raise;
end;
LThread.Resume;
end;
end;
The problem is that Delphi XE always returns the following error and does not execute anything:
First chance exception at $ 7524B727. Exception class EAccessViolation with message 'Access violation at address 00D0B9AB. Write of address 8CC38309 '. Process tester.exe (6300)
How do I fix this problem?
As David Heffernan pointed out, MessageDlgPos() cannot safely be called outside of the main UI thread, and you are not managing the thread correctly. Your code needs to look more like this instead:
type
TMyThread = class(TThread)
protected
procedure Execute; override;
public
text: string;
property ReturnValue;
end;
procedure TMyThread.Execute;
begin
// no need to check Terminated here, TThread already
// does that before calling Execute()...
TThread.Synchronize(nil,
procedure
begin
MessageDlgPos(text, mtInformation, [mbOk], 0, 100, 200);
end
);
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
LThread: TMyThread;
i: Integer;
begin
For i := 1 to 5 do
begin
LThread := TMyThread.Create(True);
LThread.text := 'hi';
LThread.FreeOnTerminate := True;
LThread.Start;
end;
end;
I would suggest a slightly different variation:
type
TMyThread = class(TThread)
private
fText: string;
protected
procedure Execute; override;
public
constructor Create(const aText: string); reintroduce;
property ReturnValue;
end;
constructor TMyThread.Create(const aText: string);
begin
inherited Create(False);
FreeOnTerminate := True;
fText := aText;
end;
procedure TMyThread.Execute;
begin
// no need to check Terminated here, TThread already
// does that before calling Execute()...
TThread.Synchronize(nil,
procedure
begin
MessageDlgPos(fText, mtInformation, [mbOk], 0, 100, 200);
end
);
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
i: Integer;
begin
For i := 1 to 5 do
begin
TMyThread.Create('hi');
end;
end;
But either way, if you don't like using TThread.Synchronize() to delegate to the main thread (thus only displaying 1 dialog at a time) then you cannot use MessageDlgPos() at all, since it is only safe to call in the main UI thread. You can use Windows.MessageBox() instead, which can be safely called in a worker thread without delegation (but then you lose the ability to specify its screen position, unless you access its HWND directly by using a thread-local hook via SetWindowsHookEx() to intercept the dialog's creation and discover its HWND):
procedure TMyThread.Execute;
begin
Windows.MessageBox(0, PChar(fText), PChar(Application.Title), MB_OK or MB_ICONINFORMATION);
);
end;
There are many problems. The biggest one is here:
LThread := TMyThread(Sender);
Sender is a button. Casting to a thread is simply wrong and the cause of your exception. Casting a button to a thread doesn't make it so. It's still a button.
You likely mean to create a thread instead.
LThread := TMyThread.Create(True);
You cannot show VCL UI outside the main thread. The call to MessageDlgPos breaks that rule. If you do need to show UI at that point, you'll need to use TThread.Synchronize to have the code execute in the main thread.
Your exception handler makes no sense to me. I think you should remove it.
Resume is deprecated. Use Start instead.
I am designing a thread pool with following features.
New thread should be spawned only when all other threads are running.
Maximum number of thread should be configurable.
When a thread is waiting, it should be able to handle new requests.
Each IO operation should call a callback on completion
Thread should have a way to manage request its serving and IO callbacks
Here is the code:
unit ThreadUtilities;
interface
uses
Windows, SysUtils, Classes;
type
EThreadStackFinalized = class(Exception);
TSimpleThread = class;
// Thread Safe Pointer Queue
TThreadQueue = class
private
FFinalized: Boolean;
FIOQueue: THandle;
public
constructor Create;
destructor Destroy; override;
procedure Finalize;
procedure Push(Data: Pointer);
function Pop(var Data: Pointer): Boolean;
property Finalized: Boolean read FFinalized;
end;
TThreadExecuteEvent = procedure (Thread: TThread) of object;
TSimpleThread = class(TThread)
private
FExecuteEvent: TThreadExecuteEvent;
protected
procedure Execute(); override;
public
constructor Create(CreateSuspended: Boolean; ExecuteEvent: TThreadExecuteEvent; AFreeOnTerminate: Boolean);
end;
TThreadPoolEvent = procedure (Data: Pointer; AThread: TThread) of Object;
TThreadPool = class(TObject)
private
FThreads: TList;
fis32MaxThreadCount : Integer;
FThreadQueue: TThreadQueue;
FHandlePoolEvent: TThreadPoolEvent;
procedure DoHandleThreadExecute(Thread: TThread);
procedure SetMaxThreadCount(const pis32MaxThreadCount : Integer);
function GetMaxThreadCount : Integer;
public
constructor Create( HandlePoolEvent: TThreadPoolEvent; MaxThreads: Integer = 1); virtual;
destructor Destroy; override;
procedure Add(const Data: Pointer);
property MaxThreadCount : Integer read GetMaxThreadCount write SetMaxThreadCount;
end;
implementation
constructor TThreadQueue.Create;
begin
//-- Create IO Completion Queue
FIOQueue := CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
FFinalized := False;
end;
destructor TThreadQueue.Destroy;
begin
//-- Destroy Completion Queue
if (FIOQueue = 0) then
CloseHandle(FIOQueue);
inherited;
end;
procedure TThreadQueue.Finalize;
begin
//-- Post a finialize pointer on to the queue
PostQueuedCompletionStatus(FIOQueue, 0, 0, Pointer($FFFFFFFF));
FFinalized := True;
end;
function TThreadQueue.Pop(var Data: Pointer): Boolean;
var
A: Cardinal;
OL: POverLapped;
begin
Result := True;
if (not FFinalized) then
//-- Remove/Pop the first pointer from the queue or wait
GetQueuedCompletionStatus(FIOQueue, A, Cardinal(Data), OL, INFINITE);
//-- Check if we have finalized the queue for completion
if FFinalized or (OL = Pointer($FFFFFFFF)) then begin
Data := nil;
Result := False;
Finalize;
end;
end;
procedure TThreadQueue.Push(Data: Pointer);
begin
if FFinalized then
Raise EThreadStackFinalized.Create('Stack is finalized');
//-- Add/Push a pointer on to the end of the queue
PostQueuedCompletionStatus(FIOQueue, 0, Cardinal(Data), nil);
end;
{ TSimpleThread }
constructor TSimpleThread.Create(CreateSuspended: Boolean;
ExecuteEvent: TThreadExecuteEvent; AFreeOnTerminate: Boolean);
begin
FreeOnTerminate := AFreeOnTerminate;
FExecuteEvent := ExecuteEvent;
inherited Create(CreateSuspended);
end;
Changed the code as suggested by J... also added critical sections but the problem i am facing now is that when i am trying call multiple task only one thread is being used, Lets say if i added 5 threads in the pool then only one thread is being used which is thread 1. Please check my client code as well in the below section.
procedure TSimpleThread.Execute;
begin
// if Assigned(FExecuteEvent) then
// FExecuteEvent(Self);
while not self.Terminated do begin
try
// FGoEvent.WaitFor(INFINITE);
// FGoEvent.ResetEvent;
EnterCriticalSection(csCriticalSection);
if self.Terminated then break;
if Assigned(FExecuteEvent) then
FExecuteEvent(Self);
finally
LeaveCriticalSection(csCriticalSection);
// HandleException;
end;
end;
end;
In the Add method, how can I check if there is any thread which is not busy, if it is not busy then reuse it else create a new thread and add it in ThreadPool list?
{ TThreadPool }
procedure TThreadPool.Add(const Data: Pointer);
begin
FThreadQueue.Push(Data);
// if FThreads.Count < MaxThreadCount then
// begin
// FThreads.Add(TSimpleThread.Create(False, DoHandleThreadExecute, False));
// end;
end;
constructor TThreadPool.Create(HandlePoolEvent: TThreadPoolEvent;
MaxThreads: Integer);
begin
FHandlePoolEvent := HandlePoolEvent;
FThreadQueue := TThreadQueue.Create;
FThreads := TList.Create;
FThreads.Add(TSimpleThread.Create(False, DoHandleThreadExecute, False));
end;
destructor TThreadPool.Destroy;
var
t: Integer;
begin
FThreadQueue.Finalize;
for t := 0 to FThreads.Count-1 do
TThread(FThreads[t]).Terminate;
while (FThreads.Count = 0) do begin
TThread(FThreads[0]).WaitFor;
TThread(FThreads[0]).Free;
FThreads.Delete(0);
end;
FThreadQueue.Free;
FThreads.Free;
inherited;
end;
procedure TThreadPool.DoHandleThreadExecute(Thread: TThread);
var
Data: Pointer;
begin
while FThreadQueue.Pop(Data) and (not TSimpleThread(Thread).Terminated) do begin
try
FHandlePoolEvent(Data, Thread);
except
end;
end;
end;
function TThreadPool.GetMaxThreadCount: Integer;
begin
Result := fis32MaxThreadCount;
end;
procedure TThreadPool.SetMaxThreadCount(const pis32MaxThreadCount: Integer);
begin
fis32MaxThreadCount := pis32MaxThreadCount;
end;
end.
Client Code :
This the client i created to log the data in text file :
unit ThreadClient;
interface
uses Windows, SysUtils, Classes, ThreadUtilities;
type
PLogRequest = ^TLogRequest;
TLogRequest = record
LogText: String;
end;
TThreadFileLog = class(TObject)
private
FFileName: String;
FThreadPool: TThreadPool;
procedure HandleLogRequest(Data: Pointer; AThread: TThread);
public
constructor Create(const FileName: string);
destructor Destroy; override;
procedure Log(const LogText: string);
procedure SetMaxThreadCount(const pis32MaxThreadCnt : Integer);
end;
implementation
(* Simple reuse of a logtofile function for example *)
procedure LogToFile(const FileName, LogString: String);
var
F: TextFile;
begin
AssignFile(F, FileName);
if not FileExists(FileName) then
Rewrite(F)
else
Append(F);
try
Writeln(F, DateTimeToStr(Now) + ': ' + LogString);
finally
CloseFile(F);
end;
end;
constructor TThreadFileLog.Create(const FileName: string);
begin
FFileName := FileName;
//-- Pool of one thread to handle queue of logs
FThreadPool := TThreadPool.Create(HandleLogRequest, 5);
end;
destructor TThreadFileLog.Destroy;
begin
FThreadPool.Free;
inherited;
end;
procedure TThreadFileLog.HandleLogRequest(Data: Pointer; AThread: TThread);
var
Request: PLogRequest;
los32Idx : Integer;
begin
Request := Data;
try
for los32Idx := 0 to 100 do
begin
LogToFile(FFileName, IntToStr( AThread.ThreadID) + Request^.LogText);
end;
finally
Dispose(Request);
end;
end;
procedure TThreadFileLog.Log(const LogText: string);
var
Request: PLogRequest;
begin
New(Request);
Request^.LogText := LogText;
FThreadPool.Add(Request);
end;
procedure TThreadFileLog.SetMaxThreadCount(const pis32MaxThreadCnt: Integer);
begin
FThreadPool.MaxThreadCount := pis32MaxThreadCnt;
end;
end.
This is the form application where i added three buttons, each button click will write some value to the file with thread id and text msg. But the problem is thread id is always same
unit ThreadPool;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ThreadClient;
type
TForm5 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Edit1Change(Sender: TObject);
private
{ Private declarations }
fiFileLog : TThreadFileLog;
public
{ Public declarations }
end;
var
Form5: TForm5;
implementation
{$R *.dfm}
procedure TForm5.Button1Click(Sender: TObject);
begin
fiFileLog.Log('Button one click');
end;
procedure TForm5.Button2Click(Sender: TObject);
begin
fiFileLog.Log('Button two click');
end;
procedure TForm5.Button3Click(Sender: TObject);
begin
fiFileLog.Log('Button three click');
end;
procedure TForm5.Edit1Change(Sender: TObject);
begin
fiFileLog.SetMaxThreadCount(StrToInt(Edit1.Text));
end;
procedure TForm5.FormCreate(Sender: TObject);
begin
fiFileLog := TThreadFileLog.Create('C:/test123.txt');
end;
end.
First, and probably most strongly advisable, you might consider using a library like OmniThread to implement a threadpool. The hard work is done for you and you will likely end up making a substandard and buggy product with a roll-your-own solution. Unless you have special requirements this is probably the fastest and easiest solution.
That said, if you want to try to do this...
What you might consider is to just make all of the threads in your pool at startup rather than on-demand. If the server is going to busy at any point then it will eventually end up with a pool of MaxThreadCount soon enough anyway.
In any case, if you want to keep a pool of threads alive and available for work then they would need to follow a slightly different model than what you have written.
Consider:
procedure TSimpleThread.Execute;
begin
if Assigned(FExecuteEvent) then
FExecuteEvent(Self);
end;
Here when you run your thread it will execute this callback and then terminate. This doesn't seem to be what you want. What you seem to want is to keep the thread alive but waiting for its next work package. I use a base thread class (for pools) with an execute method that looks something like this (this is somewhat simplified):
procedure TMyCustomThread.Execute;
begin
while not self.Terminated do begin
try
FGoEvent.WaitFor(INFINITE);
FGoEvent.ResetEvent;
if self.Terminated then break;
MainExecute;
except
HandleException;
end;
end;
end;
Here FGoEvent is a TEvent. The implementing class defines what the work package looks like in the abstract MainExecute method, but whatever it is the thread will perform its work and then return to waiting for the FGoEvent to signal that it has new work to do.
In your case, you need to keep track of which threads are waiting and which are working. You will probably want a manager class of some sort to keep track of these thread objects. Assigning something simple like a threadID to each one seems sensible. For each thread, just before launching it, make a record that it is currently busy. At the very end of your work package you can then post a message back to the manager class telling it that the work is done (and that it can flag the thread as available for work).
When you add work to the queue you can first check for available threads to run the work (or create a new one if you wish to follow the model you outlined). If there are threads then launch the task, if there are not then push the work onto the work queue. When worker threads report complete the manager can check the queue for outstanding work. If there is work it can immediately re-deploy the thread. If there isn't work it can flag the thread as available for work (here you might use a second queue for available workers).
A full implementation is too complex to document in a single answer here - this aims just to rough out some general ideas.
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.
I'm new to multithreading, but not a complete novice. I need to perform a call to a webservice in a worker thread.
In the main thread I have a form (TForm) with a private data member (private string) that only the worker thread will write to (I pass the a pointer to it into the thread before it resumes). When the worker thread has finished its webservice call and written the resultant response xml to the private member on the form, the worker thread uses PostMessage to send a message to the form's handle (which I also passed into the thread before it resumed).
interface
const WM_WEBSERVCALL_COMPLETE = WM_USER + 1;
type
TWebServiceResponseXML = string;
PWebServiceResponseXML = ^TWebServiceResponseXML;
TMyForm = class(TForm)
...
private
...
fWorkerThreadID: Cardinal;
fWebServiceResponseXML: TWebServiceResponseXML;
public
...
procedure StartWorkerThread;
procedure OnWebServiceCallComplete(var Message: TMessage); Message WM_WEBSERVCALL_COMPLETE;
end;
TMyThread = class(TThread)
private
protected
procedure Execute; override;
public
SenderHandle: HWnd;
RequestXML: string;
ResponseXML: string;
IMyService: IService;
PResponseXML: PWebServiceResponseXML;
end;
implementation
procedure TMyForm.StartWorkerThread;
var
MyWorkerThread: TMyThread;
begin
MyWorkerThread := TMyThread.Create(True);
MyWorkerThread.FreeOnTerminate := True;
MyWorkerThread.SenderHandle := self.Handle;
MyWorkerThread.RequestXML := ComposeRequestXML;
MyWorkerThread.PResponseXML := ^fWebServiceResponseXML;
MyWorkerThread.Resume;
end;
procedure TMyForm.OnWebServiceCallComplete(var Message: TMessage);
begin
// Do what you want with the response xml string in fWebServiceResponseXML
end;
procedure TMyThread.Execute;
begin
inherited;
CoInitialize(nil);
try
IMyService := IService.GetMyService(URI);
ResponseXML := IMyService.Search(RequestXML);
PResponseXML := ResponseXML;
PostMessage(SenderHandle, WM_WEBSERVCALL_COMPLETE, 0, 0);
finally
CoUninitialize;
end;
end;
It works great, but now I want to do the same thing from a datamodule (which doesn't have a Handle)... so I would really appreciate some useful code to supplement the working model I have.
EDIT
What I really want is the code (if possible) that would allow me to replace the line
MyWorkerThread.SenderHandle := self.Handle;
with
MyWorkerThread.SenderHandle := GetHandleForThisSOAPDataModule;
I have used this technique before with some success: Sending messages to non-windowed applications
Basically, use a second thread as a message pump on a handle obtained via AllocateHWND. This is admittedly irritating, and you would be better off using a library to handle all the details. I prefer OmniThreadLibrary but there are others - see How Do I Choose Between the Various Ways to do Threading in Delphi? and Delphi - Threading frameworks.
You can allocate you own handle with AllocateHwnd and use that as a PostMessage target.
TTestThread = class(TThread)
private
FSignalShutdown: boolean;
// hidden window handle
FWinHandle: HWND;
protected
procedure Execute; override;
// our window procedure
procedure WndProc(var msg: TMessage);
public
constructor Create;
destructor Destroy; override;
procedure PrintMsg;
end;
constructor TTestThread.Create;
begin
FSignalShutdown := False;
// create the hidden window, store it's
// handle and change the default window
// procedure provided by Windows with our
// window procedure
FWinHandle := AllocateHWND(WndProc);
inherited Create(False);
end;
destructor TTestThread.Destroy;
begin
// destroy the hidden window and free up memory
DeallocateHWnd(FWinHandle);
inherited;
end;
procedure TTestThread.WndProc(var msg: TMessage);
begin
if Msg.Msg = WM_SHUTDOWN_THREADS then
// if the message id is WM_SHUTDOWN_THREADS
// do our own processing
FSignalShutdown := True
else
// for all other messages call
// the default window procedure
Msg.Result := DefWindowProc(FWinHandle, Msg.Msg,
Msg.wParam, Msg.lParam);
end;
You can apply this to anything not just threads. Just beware that AllocateHWND is NOT threade safe as indicated here.
Alternatives based on the use of an event:
Use OnTerminate of the thread (already present) in combination with a flag:
TMyDataModule = class(TDataModule)
private
procedure OnWebServiceCallComplete(Sender: TObject);
...
TMyThread = class(TThread)
public
property TerminateFlag: Integer ...
...
procedure TMyDataModule.StartWorkerThread;
...
MyWorkerThread.OnTerminate := <Self.>OnWebServiceCallComplete;
...
procedure TMyDataModule.OnWebServiceCallComplete(Sender: TObject);
begin
if MyWorkerThread.TerminateFlag = WEBCALL_COMPLETE then
...
end;
Set the TerminateFlag in the Execute routine. OnTerminate will automatically fire, even if FreeOnTerminate is True.
Add a new event property to the thread class in which you may provide the flag as a parameter to indicate termination/thread result. Something like shown here. Be sure to synchronize the event call. Or forget the parameter and just only call the event if execution completed gracefully (like you're doing now).
i use this component for processing drag and drop files
http://melander.dk/delphi/dragdrop
unit DragThread;
interface
uses
Classes,DragDrop, DropTarget,DragDropFile,Dialogs,SysUtils;
type
TDragThread = class(TThread)
private
{ Private declarations }
ArraysLength : Integer;
DragComponent : TDropFileTarget;
DragArray,HashsArray : Array of string;
Procedure FDArray;
//Procedure FDHArray;
protected
procedure Execute; override;
Public
Constructor Create(Com: TDropFileTarget);
Destructor Destroy; Override;
end;
implementation
{ TDragThread }
Constructor TDragThread.Create(Com: TDropFileTarget);
begin
inherited Create(True);
DragComponent := Com;
end;
Destructor TDragThread.Destroy;
begin
//DragComponent.Free;
end;
Procedure TDragThread.FDArray;
var
A : Integer;
begin
SetLength(DragArray,DragComponent.Files.Count);
SetLength(HashsArray,DragComponent.Files.Count);
ShowMessage(IntToStr(DragComponent.Files.Count)); // just working in the first time !!
for A := 0 to DragComponent.Files.Count -1 do begin
DragArray[A] := DragComponent.Files[A];
//ShowMessage(DragComponent.Files[A]);
end;
ArraysLength := DragComponent.Files.Count-1;
//ShowMessage(DragComponent.Files[0]);
end;
procedure TDragThread.Execute;
begin
{ Place thread code here }
FDArray;
end;
end.
the strange thing that the Drop process working just one time then the DragComponent.Files.Count gives 0 for ever .!!
that's how i call it
procedure TForm1.DropFileDrop(Sender: TObject; ShiftState: TShiftState;
APoint: TPoint; var Effect: Integer);
var
DropThread : TDragThread;
begin
DropThread := TDragThread.Create(DropFile);
DropThread.Resume;
end;
i want to know why this happened and thanks in advance :) .
Don't operate VCL components from other threads.
There's no guarantee that the component's drop-event information will continue to be valid once the drop event has completed.
Copy all the information you need out of the component when you construct the thread (i.e., fully populate DragArray) and then use that cached data when executing the thread. Don't store a reference in DragComponent or you might be tempted to use it from the thread's Execute method, which you really shouldn't do.