What to use instead of TThread.Suspend() - multithreading

Suppose I have a worker thread that populates a large vector declared in the main thread. While the worker thread is still running (in response to user interaction) I want the main thread to check if the vector has been populated up to a certain size. If it has I want it to extract some values from the vector. If it hasn't i want it to wait till the worker thread populates up to the required size.
As the worker thread could still be adding items to the vector (possibly resulting in a resize/move) I'm thinking I can only do this while the worker thread is suspended but TThread.Suspend() is deprecated. I've spent days looking at TMutex, TSemaphore etc. but the documentation is dire. Could anyone point me in the right direction?
One possible solution is to populate a separate smaller vector in the worker thread and then use synchronize to append that to the large vector (and so on) but I'd like to avoid that.

As the worker thread could still be adding items to the vector (possibly resulting in a resize/move) I'm thinking I can only do this while the worker thread is suspended
That is a very good idea.
but TThread.Suspend() is deprecated.
Even when it was not deprecated, it was still dangerous to use. Only a debugger should ever suspend a thread, that is what the SuspendThread() API is intended for.
I've spent days looking at TMutex, TSemaphore etc. but the documentation is dire. Could anyone point me in the right direction?
You could simply wrap all access to the vector with a TCriticalSection or TMutex, then the main and worker threads can both enter the lock whenever they need to do anything with the vector. For example:
type
TMyThread = class(TThread)
private
FLock: TCriticalSection;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure Lock;
procedure Unlock;
end;
constructor TMyThread.Create;
begin
inherited Create(False);
FLock := TCriticalSection.Create;
end;
destructor TMyThread.Destroy;
begin
FLock.Free;
end;
procedure TMyThread.Lock;
begin
FLock.Enter;
end;
procedure TMyThread.Unlock;
begin
FLock.Leave;
end;
procedure TMyThread.Execute;
begin
while not Terminated do
begin
Lock;
try
// do something...
finally
Unlock;
end;
end;
end;
MyThread.Lock;
try
if Vector.Size >= X then
begin
// do something ...
end;
finally
MyThread.Unlock;
end;
If you find that the worker thread accesses the vector more than the main thread does, you might consider using a TMultiReadExclusiveWriteSynchronizer or a SRW lock instead.
Or, you could use some TEvent objects to signal the worker thread when to pause and when to resume. The main thread could then signal the thread to pause and wait for it to actually pause, then access the vector and unpause the thread when done. For example:
type
TMyThread = class(TThread)
private
FPauseEvent: TEvent;
FPausedEvent: TEvent;
FResumeEvent: TEvent;
procedure CheckForPause;
protected
procedure Execute; override;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure Pause;
procedure Unpause;
end;
constructor TMyThread.Create;
begin
inherited Create(False);
FPauseEvent := TEvent.Create(nil, True, False, '');
FPausedEvent := TEvent.Create(nil, True, False, '');
FResumeEvent := TEvent.Create(nil, True, True, '');
end;
destructor TMyThread.Destroy;
begin
FPauseEvent.Free;
FPausedEvent.Free;
FResumeEvent.Free;
end;
procedure TMyThread.Pause;
begin
FResumeEvent.ResetEvent;
FPauseEvent.SetEvent;
FPausedEvent.WaitFor(Infinite);
end;
procedure TMyThread.Unpause;
begin
FPauseEvent.ResetEvent;
FResumeEvent.SetEvent;
end;
procedure TMyThread.CheckForPause;
begin
if FPauseEvent.WaitFor(0) = wrSignaled then
begin
FPausedEvent.SetEvent;
FResumeEvent.WaitFor(Infinite);
FPausedEvent.ResetEvent;
end;
end;
procedure TMyThread.Execute;
begin
while not Terminated do
begin
CheckForPause;
if Terminated then Exit;
// do something...
end;
end;
MyThread.Pause;
try
if Vector.Size >= X then
begin
// do something ...
end;
finally
MyThread.Unpause;
end;

My effort below. It avoids the complexity of Remy's TEvent and the pitfalls of TCriticalSection pointed to by J..'s last comment. That's assuming it works. It does appear to but I'd be grateful if anyone could have a look for traps I may have fell into.
The user is presented with a TForm containing a TEdit called VecNdx which the user uses to enter the index he wants the vector value for and a TButton called GetVecVal which, when clicked, responds by printing the vector value for VecNdx in a TLabel called VecVal.
While the vector values themselves are generated by the rand() function you can think of them as being the results from stepping through a query result set where the size isn't known till after the last step.
.h file
#ifndef ThreadH
#define ThreadH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ComCtrls.hpp>
#include <vector>
#include <atomic>
//---------------------------------------------------------------------------
class TMainForm : public TForm
{
__published: // IDE-managed Components
TEdit *VecNdx; // user enters vector index
TButton *GetVecVal; // retreives value for vector at index entered above
TLabel *VecVal; // displays above value
void __fastcall GetVecValClick(TObject *Sender);
private: // User declarations
class TPopulate : public TThread
{
private:
TMainForm *Main;
void __fastcall ShowPopulated(void);
int Count;
clock_t ThreadStart; // clock() when thread starts running
protected:
void __fastcall Execute();
public:
__fastcall TPopulate(TMainForm *Parent) : Main(Parent) {}
} *Populate;
int VecSize=-1; // updated only after Populate finishes
std::vector<int> Vec;
std::atomic<int> UserNdx=-1,UserVal,CountSoFar;
public: // User declarations
__fastcall TMainForm(TComponent* Owner);
__fastcall ~TMainForm();
};
//---------------------------------------------------------------------------
extern PACKAGE TMainForm *MainForm;
//---------------------------------------------------------------------------
#endif
.cpp file
#include <vcl.h>
#pragma hdrstop
#include "Thread.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMainForm *MainForm;
//---------------------------------------------------------------------------
__fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner)
{
Populate=new TPopulate(this);
}
//---------------------------------------------------------------------------
__fastcall TMainForm::~TMainForm()
{
delete Populate;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::TPopulate::ShowPopulated(void)
{
Main->Caption = (Terminated ? String("Terminated after ") : String(Count)+" values in ")
+((clock()-ThreadStart)/CLOCKS_PER_SEC)+" secs";
Main->VecSize=Count;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::TPopulate::Execute()
{
ThreadStart=clock();
const int Mx=100000000;
Count=0;
for (int u; !Terminated && Count<Mx;)
{
Main->Vec.push_back(rand() % Mx);
Count++;
if ((u = Main->UserNdx) != -1)
{
if (Count>u) Main->UserVal=Main->Vec[u];
else Main->CountSoFar=Count;
Main->UserNdx=-1;
}
}
Synchronize(ShowPopulated);
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::GetVecValClick(TObject *Sender)
{
int Ndx=VecNdx->Text.ToIntDef(-1);
if (Ndx<0 || (VecSize>=0 && Ndx>=VecSize)) throw Exception("Range Error");
if (VecSize>=0) VecVal->Caption=Vec[Ndx];
else
{
CountSoFar=0; // if Populate changes CountSoFar => Vec[UserNdx] not yet assigned
UserNdx=Ndx;
while (UserNdx!=-1); // Ensure Populate processes UserNdx
VecVal->Caption = CountSoFar ? "Populated only to "+String(CountSoFar) : int(UserVal);
}
}
//---------------------------------------------------------------------------

Related

Multi thread safe event in Delphi

I'm beginner in Delphi programing.
I use a thread to communicate with my server and I want to pass a Tevent to my thread at creation time and use it to signal a task on this thread from main thread and finally on thread clear the event. This event set in main thread to signal the task on my net thread and finally clear after task completed in the net thread.
I use this line to create thread on run time. All work fine but after adding event to my code rise a problem.
Net_thread:= TNetThread.Create(user, password, TheCallback, Tevent);
TNetThread is my thread class on other unit and Net_thread is my net thread.
TheCallback is a procedure for change UI from thread. Declare this type in TNetThread.
user, password are login data collected in GUI.
Tevent is a handle to my event created in main thread and pass it to Net_thread.
Before I add event to my code I only pass 2 string and a procedure to thread and I have no problem. ...Create(user, password, TheCallback); after add event to my code and pass it as THandle to my thread can not use it. Its like a Cardinal variable and when I try to check its state with this code:
System.SyncObjs.TEvent.WaitFor(FEvent)
I have an error e2076. FEvent set on constructor TNetThread.Create and equal to Tevent received from main thread.
Please give me a simple example?
This is my minimal code:
on main form:
procedure TMainform.FormCreate(Sender: TObject);
var
T_event: THandle;
begin
T_event: := CreateEvent(nil, True, False, nil);
Net_thread:= TNetThread.Create(user, password, TheCallback, T_event);
end;
procedure TMainform.TheCallback(const st,h : String);
begin
//recive data from net thread
end;
on event in main thread
SetEvent(T_event);
And on other unit
type
TMyCallback = procedure(const st, h : String) of object;
TNetThread = class(TThread)
IdTCPClient1: TIdTCPClient;
private
FCallback : TMyCallback;
FEvent: THandle;
protected
procedure execute; override;
procedure SendLog(st, h: string);
public
constructor Create(user_n, psw: string ;aCallback : TMyCallback ; const AEvent: THandle);
end;
constructor TNetThread.Create(user_n, psw: string ;aCallback: TMyCallback; const AEvent: THandle);
begin
inherited Create(false);
FCallback := aCallback;
FEvent := AEvent;
user_name := user_n;
password:= psw;
FreeOnTerminate := true;
end;
procedure TNetThread.SendLog(st ,h: string);
begin
if not Assigned(FCallback) then
Exit;
Self.Queue( // Executed later in the main thread
procedure
begin
FCallback(st, h);
end
);
end;
procedure TNetThread.Execute;
begin
.
.
if (System.SyncObjs.TEvent.WaitFor(FEvent) = wrSignaled) then...
.
.
end;

Show file system tree data in TVirtualStringTree

I have such thread safe class for file system objects:
type
PFSObject = ^TFSObject;
TFSObject = class
private
FMREW: TMREWSync;
FChildren: TObjectList<TFSObject>;
FFilesCount: UInt32;
FFoldersCount: UInt32;
FName: string;
FParent: TFSObject;
function GetFullPath: string;
public
constructor Create(const AName: string; AParent: TFSObject; AFilesCount, AFoldersCount: UInt32 = 0);
destructor Destroy; override;
property Children: TObjectList<TFSObject> read FChildren write FChildren;
property FilesCount: UInt32 read FFilesCount write FFilesCount;
property FoldersCount: UInt32 read FFoldersCount write FFoldersCount;
property Name: string read FName write FName;
property Parent: TFSObject read FParent write FParent;
procedure LockRead;
procedure LockWrite;
procedure UnlockRead;
procedure UnlockWrite;
end;
Have thread, which scan file system and fill this.
On the main form have Timer, which receiving data from this class to show in TVirtualStringTree.
Which is the best method to show such data in TVirtualStringTree without loosing additional memory to store copy of data in Nodes?
Update:
Ok, what I have now.
type
PSizeData = ^TSizeData;
TSizeData = record
FSObj: PFSObject;
end;
// OnTimer reader
procedure TformSize.tmrSizeTimer(Sender: TObject);
begin
if tvSize.RootNodeCount = 0 then
tvSize.RootNodeCount := 1
else begin
tvSize.Repaint;
if FSThread.Finished then begin
// Thread finished, disable timer
SetTimerEnabled(False);
// Expant first node
tvSize.Expanded[tvSize.GetFirst] := True;
end;
end;
end;
// GetText of TVirtualStringTree
procedure TformSize.tvSizeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
TextType: TVSTTextType; var CellText: string);
var
Data, ParData: PSizeData;
begin
// Check that children count changed for node
Data := tvSize.GetNodeData(Node);
if (Int32(Node.ChildCount) <> Data.FSObj.Children.Count) then begin
tvSize.ChildCount[Node] := Data.FSObj.Children.Count;
// Check that children count changed for parent node
ParData := tvSize.GetNodeData(Node.Parent);
if Assigned(ParData) and (Int32(Node.Parent.ChildCount) <> ParData.FSObj.Children.Count) then
tvSize.ChildCount[Node.Parent] := ParData.FSObj.Children.Count;
end;
// Get node text
CellText := GetSizeDataText(Data, Column);
end;
// InitNode of TVirtualStringTree
procedure TformSize.tvSizeInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
var InitialStates: TVirtualNodeInitStates);
var
Data, ParData: PSizeData;
PFSObj: PFSObject;
begin
Data := Sender.GetNodeData(Node);
if not Assigned(ParentNode) then
PFSObj := #FSThread.FSObject
else begin
ParData := Sender.GetNodeData(ParentNode);
PFSObj := PFSObject(ParData.FSObj.Children[Node.Index]);
end;
Data.FSObj := PFSObj;
end;
And now I have out of memory in TVirtualStringTree :(
where is my error?
That's for sure a threading issue between the VCL main thread and the file reading / writing thread.
When using VCL (remember: TVirtualStringTree is a VCL component) you need to synchronize additional threads with the VCL main thread.
What i've done in the past to avoid these kind of issues:
create a mutex in the VCL main thread (aka your TForm or something)
create the thread and pass the mutex to it
run the thread and when accessing VCL properties do a mutex lock before and a mutex unlock after
in VCL main thread do a mutex lock / unlock also
Basically you should never access or change VCL properties from additional threads without secure synchronization.

Assigning object to another thread

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).

Delphi 5 + BDE + TStoredProc + Oacle + Thread

i'm trying to develop a application that runs a stored procedure in a background mode.
1 - If i run in the mains thread, it's OK, it's lock the screen.
2 - If i put the stpGen.ExecProc; in a thread, it's runs OK, but it's lock the mains thread too.
is there a option to run TStoredProc in a thread and don't lock the main thread?
Code:
type
TStoredProcSegundoPlano = class(TThread)
private
stpGen: TStoredProc;
excErro: Exception;
protected
procedure Execute; override;
public
constructor Create(const stpGen: TStoredProc; const blnAutoFree, blnAutoStart: Boolean);
property Erro: Exception read excErro;
end;
implementation
{ TStoredProcSegundoPlano }
constructor TStoredProcSegundoPlano.Create(const stpGen: TStoredProc;
const blnAutoFree, blnAutoStart: Boolean);
begin
inherited Create(True);
Self.excErro := nil;
Self.stpGen := stpGen;
if blnAutoFree then
FreeOnTerminate := True;
if blnAutoStart then
Resume;
end;
procedure TStoredProcSegundoPlano.Execute;
begin
try
try
stpGen.ExecProc;
except
on E: Exception do
begin
excErro := AcquireExceptionObject;
end;
end;
finally
Terminate;
end;
end;
so, i call this way:
TStoredProcSegundoPlano.Create(stpGen, True, True);
in this moment, the application is ok, but few seconds after, the maisn threads lock, i don't have any idea know to resolve this, it's possivel to run a TStoredProc un a background?
Thanks

How can a thread notify an object that doesn't have a window handle?

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).

Resources