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