jaja2005
asked on
Boss Thread and worker threads communication
Question: Hi. In my main application (main VCL) I want to develop a single boss
thread which:
A: is able to communicate with worker threads
B: is able to update some VCL componets properties into main VCL basing
on the worker thread results.
C: create the worker thread and limit the number of it using a semaphore
Worker threads are I/O operation. They do some test, create a sort of
array of boolean based on the test performed and store into a shared
buffer (es. TthreadList).
The approach I would like to use is the one of producer/consumer.
So the sequence should be:
- The boss spaws some worker threads
- The workers threads do some test and create a result as [TRUE,FALSE,FALSE...]
with a size that is dynamic.
- the worker thread store it into a shared buffer, protected with Critical Section.
- The boss thread wakes up or wait to access to the buffer. When it does it
will update the propreties in the main VCL
I want to develop this structure without using Syncronize and above all
limiting the buffer size and taking care of flow control between boss an
workers.
thread which:
A: is able to communicate with worker threads
B: is able to update some VCL componets properties into main VCL basing
on the worker thread results.
C: create the worker thread and limit the number of it using a semaphore
Worker threads are I/O operation. They do some test, create a sort of
array of boolean based on the test performed and store into a shared
buffer (es. TthreadList).
The approach I would like to use is the one of producer/consumer.
So the sequence should be:
- The boss spaws some worker threads
- The workers threads do some test and create a result as [TRUE,FALSE,FALSE...]
with a size that is dynamic.
- the worker thread store it into a shared buffer, protected with Critical Section.
- The boss thread wakes up or wait to access to the buffer. When it does it
will update the propreties in the main VCL
I want to develop this structure without using Syncronize and above all
limiting the buffer size and taking care of flow control between boss an
workers.
Hi, jaja2005,
I've made a little project for you (but I haven't finished the tests yet). See if it is something like this that you want.
To compile it, create the files which name are surrounding by '*******'.
Regards,
Douglas.
I've made a little project for you (but I haven't finished the tests yet). See if it is something like this that you want.
To compile it, create the files which name are surrounding by '*******'.
Regards,
Douglas.
************************
TestThread.dpr
***********************
program ThreadTest;
uses
Forms,
Mainform in 'Mainform.pas' {FMainform},
BossWorkerThreads in 'BossWorkerThreads.pas';
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TFMainform, FMainform);
Application.Run;
end.
********************************
Mainform.dfm
********************************
object FMainform: TFMainform
Left = 192
Top = 103
Width = 696
Height = 480
Caption = 'Thread Test'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 32
Top = 24
Width = 113
Height = 25
Caption = 'Create Boss Thread'
TabOrder = 0
OnClick = Button1Click
end
object Memo1: TMemo
Left = 24
Top = 64
Width = 257
Height = 321
TabOrder = 1
end
object Button2: TButton
Left = 328
Top = 24
Width = 169
Height = 25
Caption = 'Resume Boss Thread'
TabOrder = 2
OnClick = Button2Click
end
object Button3: TButton
Left = 168
Top = 24
Width = 153
Height = 25
Caption = 'Suspend Boss Thread'
TabOrder = 3
OnClick = Button3Click
end
end
********************************
Mainform.pas
********************************
unit Mainform;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TFMainform = class(TForm)
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button3Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
FMainform: TFMainform;
implementation
{$R *.dfm}
uses BossWorkerThreads;
var
b: TBossThread;
procedure TFMainform.Button1Click(Sender: TObject);
begin
b := TBossThread.Create(3, Self);
end;
procedure TFMainform.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if b <> nil then
b.Terminate;
end;
procedure TFMainform.Button3Click(Sender: TObject);
begin
if b <> nil then
b.Suspend;
end;
procedure TFMainform.Button2Click(Sender: TObject);
begin
if b <> nil then
b.Resume;
end;
end.
****************************
BossWorkerThreads.pas
****************************
unit BossWorkerThreads;
interface
uses
SysUtils, Classes, SyncObjs, Windows, Mainform, Dialogs;
type
TWorkerThread = class;
TWorkerThreadResult = record
WorkerThread: TWorkerThread;
WorkerThreadID: Cardinal;
Result: Boolean;
end;
PWorkerThreadResult = ^TWorkerThreadResult;
TBossThread = class(TThread)
private
FFMainform: TFMainform;
FSemaphore: Integer;
FEvent: TEvent;
FWorkerResults: TThreadList;
FWorkerThreadList: TList;
{ Private declarations }
protected
procedure Execute; override;
function SpawnThread: Boolean;
public
constructor Create(Semaphore: Integer; FMainform: TFMainform);
destructor Destroy; override;
procedure AddWorkerThreadResult(WorkerThreadResult: TWorkerThreadResult);
property Event: TEvent read FEvent;
end;
TWorkerThread = class(TThread)
private
FBossThread: TBossThread;
{ Private declarations }
function DoTest(TestFlag: Boolean): Boolean;
protected
procedure Execute; override;
public
constructor Create(BossThread: TBossThread);
end;
implementation
{ TBossThread }
procedure TBossThread.AddWorkerThreadResult(WorkerThreadResult: TWorkerThreadResult);
begin
with FWorkerResults do
begin
with LockList do
try
Add(@WorkerThreadResult);
FEvent.SetEvent;
finally
UnlockList;
end;
end;
end;
constructor TBossThread.Create(Semaphore: Integer; FMainform: TFMainform);
begin
inherited Create(True);
FWorkerResults := TThreadList.Create;
FWorkerThreadList := TList.Create;
FEvent := TEvent.Create(nil, False, False, '');
FSemaphore := Semaphore;
FFMainform := FMainform;
FreeOnTerminate := True;
Resume;
end;
destructor TBossThread.Destroy;
var
i: Integer;
begin
for i := 0 to FWorkerThreadList.Count - 1 do
TWorkerThread(FWorkerThreadList[i]).Terminate;
FWorkerResults.Free;
FEvent.Free;
inherited;
end;
procedure TBossThread.Execute;
var
List: TList;
WorkerThreadResult: PWorkerThreadResult;
FAvailableThreads: Boolean;
begin
while not Terminated do
begin
repeat
FAvailableThreads := SpawnThread
until not FAvailableThreads;
List := FWorkerResults.LockList;
try
if List.Count > 0 then
begin
WorkerThreadResult := List.Items[0];
FFMainform.Memo1.Lines.Add('ThreadID = ' + IntToStr(WorkerThreadResult^.WorkerThreadID) + ', ' +
'Result = ' + IntToStr(Integer(WorkerThreadResult^.Result)));
FWorkerResults.Remove(WorkerThreadResult);
end
finally
FWorkerResults.UnlockList;
if List.Count = 0 then
begin
FEvent.ResetEvent;
FEvent.WaitFor(INFINITE);
end;
end;
end;
end;
function TBossThread.SpawnThread: Boolean;
begin
Result := False;
if FSemaphore > 0 then
begin
FWorkerThreadList.Add(TWorkerThread.Create(Self));
Dec(FSemaphore);
Result := True;
end;
end;
{ TWorkerThread }
constructor TWorkerThread.Create(BossThread: TBossThread);
begin
inherited Create(True);
FBossThread := BossThread;
FreeOnTerminate := True;
Resume;
end;
function TWorkerThread.DoTest(TestFlag: Boolean): Boolean;
begin
Result := not TestFlag;
end;
procedure TWorkerThread.Execute;
var
WorkerThreadResult: TWorkerThreadResult;
TestFlag: Boolean;
begin
TestFlag := False;
with WorkerThreadResult do
begin
WorkerThread := Self;
WorkerThreadID := ThreadID;
end;
while not Terminated do
begin
TestFlag := DoTest(TestFlag);
WorkerThreadResult.Result := TestFlag;
FBossThread.AddWorkerThreadResult(WorkerThreadResult);
end;
end;
end.
ASKER
Good day dougaug!
Great, many thanks. I am going to study and test it and let you know my doubts.
See ya
Great, many thanks. I am going to study and test it and let you know my doubts.
See ya
ASKER
Just one thing i have noticed. You use TestFlag: Boolean a single variable while i want to work with an array or list of bolean. Let me explain you my goal. At beginning the BossThread copies a sort of
array or list of control to be performed (maybe via worker constructor) kind of:
A (String) - B (char or string) - C (String or Integer)
An example:
Check if ValueofTemperature is > of 30
ValueofTemperature is 'A'
'>' is B
30 is 'C'
So each worker thread will get its tasks to be completed from the BossThread. One workerthread may receive 10 controls to verify other less or maybe more. So the workerthread would create a sort
of array or TList of Bolean of results internally. Once the thread is done itwill inform the boss thread(maybe with postmessage) and copies the TList in a shared buffer (maybe before the thread destroy itself (OnTerminate=True)). Now the Bossthread may picks up an item from the buffer ( where a single item is a array of TList of boolean seen before) and upadate a TListBox or whatever in my VCL main thread.
Hope it helps.
Thx
array or list of control to be performed (maybe via worker constructor) kind of:
A (String) - B (char or string) - C (String or Integer)
An example:
Check if ValueofTemperature is > of 30
ValueofTemperature is 'A'
'>' is B
30 is 'C'
So each worker thread will get its tasks to be completed from the BossThread. One workerthread may receive 10 controls to verify other less or maybe more. So the workerthread would create a sort
of array or TList of Bolean of results internally. Once the thread is done itwill inform the boss thread(maybe with postmessage) and copies the TList in a shared buffer (maybe before the thread destroy itself (OnTerminate=True)). Now the Bossthread may picks up an item from the buffer ( where a single item is a array of TList of boolean seen before) and upadate a TListBox or whatever in my VCL main thread.
Hope it helps.
Thx
Ok I have understood. I will try to do something like you said and post the code here.
Regards
Regards
ASKER
Great, in meanwhile i will study the previous example
Thanks a lot for your help!
Thanks a lot for your help!
dougaug, jaja2005
let me point out some things:
first of all,
you have circular unit reference :(
this should by avoided between the mainform and the bossthread
second, you are updating the VCL from within a other thread !
this will hang up your app !!!
it's a nice start, but unforatunately will provide some problems
i'll rebuild it for you so you can use the bossthread from any form !
and show you how to do a callback to a higher object
check this article on to use a callback:
https://www.experts-exchange.com/articles/Programming/Languages/Pascal/Delphi/Displaying-progress-in-the-main-form-from-a-thread-in-Delphi.html
it works for any form
this works with a synchronised callback, but we can change this too
i will use a object to return info
let me work on this a little ...
let me point out some things:
first of all,
you have circular unit reference :(
this should by avoided between the mainform and the bossthread
second, you are updating the VCL from within a other thread !
this will hang up your app !!!
it's a nice start, but unforatunately will provide some problems
i'll rebuild it for you so you can use the bossthread from any form !
and show you how to do a callback to a higher object
check this article on to use a callback:
https://www.experts-exchange.com/articles/Programming/Languages/Pascal/Delphi/Displaying-progress-in-the-main-form-from-a-thread-in-Delphi.html
it works for any form
this works with a synchronised callback, but we can change this too
i will use a object to return info
let me work on this a little ...
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thank guys! Well it seems that I will have lot of thing to read before my holiday...:-)
see ya
see ya
ASKER
Hi Geert, I am studyng you code and note all the question or doubts I have.
Should I expect some other code from you?
>> it's not completed....
see u.
Should I expect some other code from you?
>> it's not completed....
see u.
ASKER
ah..i forgot to tell u. I will be on holiday next 15 days.
If I will not renew the service now can I do it later on and access again to my open question and continue?
Thx
If I will not renew the service now can I do it later on and access again to my open question and continue?
Thx
i'll be on holiday too for 3 weeks
i'll be working on this project for myself
i don't know when exactly it will be finished
i'll post the changes in the future, possibly in a article too
i'll be working on this project for myself
i don't know when exactly it will be finished
i'll post the changes in the future, possibly in a article too
ASKER
ok see you soon and enjoy your holiday.
ASKER
Hi All. I am back.
I have been testing your code. I have a couple of question.
As Task I want to ping a node and get the result. I've added
a new components for this in TWorkerThread class as below:
constructor TWorkerThread.Create(Creat eSuspended : Boolean; aSignalMsgEvent: TSignalWorkerEvent = nil; aUseSynchronize: boolean = True);
begin
inherited Create(CreateSuspended);
CreateMsgInfo;
fSignalMsgEvent := aSignalMsgEvent;
fUseSynchronize := aUseSynchronize;
FPing := TipwPing.Create(nil);
with FPing do begin
PacketSize := 32;
Timeout := 60; // Abandon ping attempt after 10 second
//Idle := True;
Tag := 0;
OnResponse := PingThreadResponse; FOnError := PingThreadError;
end;
procedure TWorkerThread.PingThreadRe sponse(Sen der: TObject; RequestId: Integer;
const ResponseSource, ResponseStatus: string; ResponseTime: Integer);
begin
FThreadPingResult := ResponseStatus;
if (ResponseStatus = 'OK') then
SignalStatus(' Node is UP');
SignalEnd;
end;
procedure TWorkerThread.DoAction;
begin
FPing.PingHost('127.0.0.1' );
end;
procedure TWorkerThread.Execute;
begin
SignalBegin;
try
try
DoAction;
except
on e: Exception do
SignalError (e.Message);// Always fired????
end;
finally
SignalEnd;
end;
end;
FPing components works like that:
if Timeout is >0 the component blocks. If the Timeout property is set to 0, all operations return immediately, potentially failing with an error if they can't be completed immediately.
- If I specify an IP not reachable then i get correctly Timeout message with SignalError (e.Message) after 10s.
- If I specify a valid IP i.e. 127.0.0.1 I got:
- The message "Node is UP" but only after "Timeout seconds"
- I get also the message Timeout 301 along with "Node is UP", exception is always fired.
Any suggestion?
What's wrong?
:-(
Thx
I have been testing your code. I have a couple of question.
As Task I want to ping a node and get the result. I've added
a new components for this in TWorkerThread class as below:
constructor TWorkerThread.Create(Creat
begin
inherited Create(CreateSuspended);
CreateMsgInfo;
fSignalMsgEvent := aSignalMsgEvent;
fUseSynchronize := aUseSynchronize;
FPing := TipwPing.Create(nil);
with FPing do begin
PacketSize := 32;
Timeout := 60; // Abandon ping attempt after 10 second
//Idle := True;
Tag := 0;
OnResponse := PingThreadResponse; FOnError := PingThreadError;
end;
procedure TWorkerThread.PingThreadRe
const ResponseSource, ResponseStatus: string; ResponseTime: Integer);
begin
FThreadPingResult := ResponseStatus;
if (ResponseStatus = 'OK') then
SignalStatus(' Node is UP');
SignalEnd;
end;
procedure TWorkerThread.DoAction;
begin
FPing.PingHost('127.0.0.1'
end;
procedure TWorkerThread.Execute;
begin
SignalBegin;
try
try
DoAction;
except
on e: Exception do
SignalError (e.Message);// Always fired????
end;
finally
SignalEnd;
end;
end;
FPing components works like that:
if Timeout is >0 the component blocks. If the Timeout property is set to 0, all operations return immediately, potentially failing with an error if they can't be completed immediately.
- If I specify an IP not reachable then i get correctly Timeout message with SignalError (e.Message) after 10s.
- If I specify a valid IP i.e. 127.0.0.1 I got:
- The message "Node is UP" but only after "Timeout seconds"
- I get also the message Timeout 301 along with "Node is UP", exception is always fired.
Any suggestion?
What's wrong?
:-(
Thx
ASKER
Want to be sure to undestand you code.
Please check the steps below;
1. First you inizialize the TCollection (TTaskItems) and the Critical Section objects. At very first time the Tcollection is empty.
2. You start the BossThread and in the constructor you set some fSignalMsgEvent to point to ReturningInfo in MainForm. the fevent is created.
3. BossThread starts chekings for newtask (CheckNewTasks) to be excute in TCollection (TTaskItems). for it you use wrTimeout which value can be easly changed.
4. By Clicking on AddWorker you create an instace of TTask class and sets fStatus in tsNotStarted Status.
5. By using AddTask(x) you add a new TTaskItem to TTaskItems with this code:
with mTasks.Add do
fTask := aTask;
TBossThread.Warning;
mTasks.Add automactly creates an object of TTaskItem. You copy x in fTask, which is private field of TTaskItem. if I understood correctly your approach the real job to be done is specified by ftask field right? Have seen other example where TTaskItem was create like:
TPingItem = class (TCollectionItem)
public
PingThreadId : Integer;
PingThread : TPingThread;
...
In the example above PingThead has events to be fired like PingOnReply, PinOnStatus and so on...
Your decided to use class reference in order to change the type of WorkerThread so job to be done
in DoAction right?
Please check the steps below;
1. First you inizialize the TCollection (TTaskItems) and the Critical Section objects. At very first time the Tcollection is empty.
2. You start the BossThread and in the constructor you set some fSignalMsgEvent to point to ReturningInfo in MainForm. the fevent is created.
3. BossThread starts chekings for newtask (CheckNewTasks) to be excute in TCollection (TTaskItems). for it you use wrTimeout which value can be easly changed.
4. By Clicking on AddWorker you create an instace of TTask class and sets fStatus in tsNotStarted Status.
5. By using AddTask(x) you add a new TTaskItem to TTaskItems with this code:
with mTasks.Add do
fTask := aTask;
TBossThread.Warning;
mTasks.Add automactly creates an object of TTaskItem. You copy x in fTask, which is private field of TTaskItem. if I understood correctly your approach the real job to be done is specified by ftask field right? Have seen other example where TTaskItem was create like:
TPingItem = class (TCollectionItem)
public
PingThreadId : Integer;
PingThread : TPingThread;
...
In the example above PingThead has events to be fired like PingOnReply, PinOnStatus and so on...
Your decided to use class reference in order to change the type of WorkerThread so job to be done
in DoAction right?
i stopped my further work on the thread boss and worker
i'm gonna use this for myself:
http://otl.17slon.com/
i'm gonna use this for myself:
http://otl.17slon.com/
ASKER
Hi. Hmm, I've studied deeply your code and i would like to have things working. The project works nice unless i use in DoAction a sample task (as you have done by using a repeat-until loop). As you can see I added a FPing component as private filed in WorkerThread. The result I got is described above.
ID: 25367082
The example you posted it has been very useful to me to start to undestand the mechanims of exchanging signal among theads, I have to admit. I am very interested in that and learn a good approach for running task and having work done within a workerthread back to mainVCL or BossThread using delphi events. The structure would be very useful to be implemented
in different scenario where task might be everything.
Why you moved to OmniThreadLibrary? Have you been able to get the same result with Omni? Can you post the code for the example of lunching task from a BossThread?
Do you have examples project on this topic?
I was wating for an article of your on that..:-((
thx
ID: 25367082
The example you posted it has been very useful to me to start to undestand the mechanims of exchanging signal among theads, I have to admit. I am very interested in that and learn a good approach for running task and having work done within a workerthread back to mainVCL or BossThread using delphi events. The structure would be very useful to be implemented
in different scenario where task might be everything.
Why you moved to OmniThreadLibrary? Have you been able to get the same result with Omni? Can you post the code for the example of lunching task from a BossThread?
Do you have examples project on this topic?
I was wating for an article of your on that..:-((
thx
ASKER
Hi Geert.
It must be something wrong with FPing components. Indy Ping works nice.
Let you know.
It must be something wrong with FPing components. Indy Ping works nice.
Let you know.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I use Delphi 2006., probably it won't work. I have compiled the package and have now in palette only TOmniEventMonitor...it's all that I need...? :-(
lol, same here, threads are not visual components with events to attach
it's possible it will never be more on the palette
it's possible it will never be more on the palette
a thread shouldn't update anything in the VCL if not using synchronize or lock
instead you should let the form update itself (not in a thread)
rather give the form the information it needs in a object instance and send it a message the data changed