where : ibrtses delphi
Threads, thread objects and its use
disclaimer
the source code of this page may not appear correctly in certain browsers
due to special characters. Have a look at the source of this HTML page
with notepad instead
As Delphi focuses more on database stuff and less on communication with embedded systems,
the application, support and description of threads and thread objects is rather short.
I'll focus here less on spreading CPU intense applications on several CPUs than on
using threads to simplify communication applications.
The synchonization objects are not really supported in the professional version of Delphi,
meaning the objects are there but without the documentation. It requires the enterprise
version. Marketingwise strange since the enterprise version is database oriented and
not targetted at engineers. Multithreaded applications gain from the methods taught
to engineers.
Introduction
Consider an external device, that has to be polled for some data.
You cannot be sure that it is there, meaning the app should not crash due
to a temporarily missing device. First you send a command, then
give it some time to process and finally send a reply request and get
some data. Not yet convincing to use threads.
Consider there are hundreds of external devices. Scheduling the messages
becomes a nightmare. The solution is to use threads. One thread for an
open device allows a clear program structure. Even conditions, such
as 'only one command per device at a time' do not add much complexity.
example
procedure devicethread;
var DeviceId:integer;
success:boolean;
begin
while true do begin
DeviceId:=GetNextDevice;
Command:=GetCommand(DeviceId); // perhaps from a table
Sendcommand(DeviceId,Command);
Sleep(..); // predefined time according to command
Sendcommand(DeviceId,ReplyReq);
success:=WaitForReply(Reply,timeout);
if success then ProcessReply
else ...
end;
end;
Most of the time, the thread sleeps for the device to process the command.
Therefore this thread can be started in parallel to fill the bandwidth of the
communication.
working with threads
A standard thread is defined as :
TMyThread=class(TThread)
procedure execute; override;
end;
it runs in the memory of the main program and can therefore access the variables there.
As it is, the above thread is quite useless as there is no interaction with the main program.
movable window
A movable window during some heavy processing is achieved through letting the thread do
the calculations. Such as :
TMyThread=class(TThread)
i,j,k,m:integer;
procedure execute; override;
procedure show;
end;
procedure TMyThread.execute;
begin
i:=0; j:=0; k:=0; m:=0;
while not terminated do begin // just spend some time somehow
for i:=0 to 100000000 do begin // ahem : our heavy processing
for j:=0 to 100000000 do inc(k);
end;
inc(m);
synchronize(show); // writes m to the memo of the app
end; // while not terminated
end;
procedure TMyThread.showc;
begin
form1.memo1.lines.add(inttostr(m));
end;
sending messages from a thread to the main app
by sending messages from the thread to the main, the thread can use
methods of the main.
const
MyMsg=WM_USER+1;
TMyThread=class(TThread)
procedure execute; override;
end;
TForm1=class(TForm)
..
procedure OnMyMsg(var M:TMessage); message MyMsg;
end;
procedure TForm1.OnMyMsg(var M:TMessage); // display the string on a memo
var msgstrptr:PShortstring;
begin
msgstrptr:=ptr(M.wparam);
memo1.lines.add(msgstrptr^);
dispose(msgstrptr);
end;
procedure TMyThread.execute;
procedure postmainmessage(msg:shortstring);
var msgstrptr:PShortstring;
begin
new(msgstrptr);
msgstrptr^:=msg;
postmessage(form1.handle,MyMsg,integer(msgstrptr),0);
end;
begin
while not terminated do begin // send 'X' once a second to the main
postmainmessage('X');
sleep(1000);
end;
end;
in this example, the TForm1.OnMyMsg is used to display the character 'X' in the Forms memo every second.
post a message to a thread
Sending from the main or a thread is done as :
const AMsg=WM_USER+2;
procedure TForm1.PostMsgToThread(m:shortstring);
var msg1:PShortstring;
begin
new(msg1);
msg1^:=m;
postthreadmessage(mythreadid,AMsg,integer(msg1),0);
end;
the thread looks like:
TMyThread=class(TThread)
msg:shortstring;
timer1:TTimer;
procedure execute; override;
procedure Timer1Timer(Sender:TObject);
end;
procedure TMyThread.Timer1Timer(Sender:TObject); // required as dummy
begin
end;
procedure TMyThread.execute;
var TmpMsg:TMsg;
PMsg:PShortString;
begin
Timer1:=TTimer.create(nil);
Timer1.Interval:=500;
Timer1.enabled:=true;
form1.mythreadid:=getcurrentthreadid;
peekmessage(TmpMsg,0,WM_USER,WM_USER,PM_NOREMOVE); // force message queue
while getmessage(TmpMsg,0,0,0) do begin
PMsg:=ptr(TmpMsg.wparams);
msg:=pmsg^;
.. do something with msg ..
dispose(pmsg);
dispatchmessage(TmpMsg);
if terminated break;
end;
Timer1.enabled:=false;
Timer1.free;
end;
The building stones
Multithreaded applications consist of threads, signals, waits, and resource protectors.
Signals
Of the various possible signals the TEvent represents a binary Semaphore that works
within an application.
Var MyEvent:TEvent;
MyEvent=TEvent.create(NIL,false,false,'MyEventName');
MyEvent.SetEvent; //is used to signal the event
MyEvent.ResetEvent; // manual reset
MyEvent.WaitFor; // waits for the event to signal
Beside the TEvents, there are Semaphores to signal an event. While TEvents work within the application
Protecting a resource
A resource is protected by single threaded access code. This is achieved by a
critial section. Only one process can enter a critical section at once.
In the following example either the queue_in is active or the queue_out.
This way two threads can access a queue independently without corrupting the data.
Should the queue being accessed, the enter is blocking until the other thread
leaves the critical section.
Var cs:TCriticalSection;
cs:=TCriticalSection.create;
procedure queue.queue_in(data..);
begin
cs.enter;
.. manipulate the queue ..
cs.leave;
end;
procedure queue.queue_out(var data ...
begin
cs.enter;
.. manipulate the queue ..
cs.leave;
end;
Beside a critical section there are Mutextes and Semaphores available to protect
some resource.
Waiting
Beside waiting for a TEvent, there are WaitForSingleObject and WaitforMultipleObjects
WaitForSingleObject allows waiting for a variety of events : ChangeNotification, ConsoleInput,
Event, Mutex, Process termination, Semaphore, Thread termination, Timer.
WaitforSingleObject(Handle_of_the_object,timeout);
WaitforMultipleObjects can wait for a number of events at the same time.
It is what the ADA 'select' statement does.
WaitForMultipleObjects(NumberOfEvents,PtrToHandleArray,WaitAll,timeout);
to be continued ...
Additional pages :
timerqueue
Feedback is welcome
sponsored links
Delphi index
home
last updated: 22.june.04, or perhaps later
Copyright (99,2004) Ing.Büro R.Tschaggelar