TAxThread - Inter thread message based communication - Delphi

http://www.cybletter.com/index.php?id=3

http://www.cybletter.com/index.php?id=30

Source Code

http://www.cybletter.com/index.php?s=file_download&id=3

Full Paper

www.cybletter.com/index.php?s=file_download&id=4 

Alexandr Štefek
Military Academy in Brno
alexandr@stefek.cz

Abstract

This paper present implementation of parallel execution.

If we use parallel proces in programs we have to solve synchronization.

We show the effective implementation of parallel execution without using critical sections, semaphores, mutexes or events.

Thread

The thread is system object defined on platform Win32.

SDK defines some method for thread creating and handling.

In real implementation must be defined the procedure for all parallel procedures.

If in system Win32 thread create new window handle then all messages are handled by this thread.

For this fact commes the idea of problem solution.

We can simply create window handle on executing thread.

When this thread executes loop for message handling it is possible to send special message to thread window handle.

The parameters of message can be method to execute and parameter for this method.

But method has 8 bytes and the message parameter only 4.

So we have allocate memory block, copy method to this block and send adress of allocated block.

Coding the method

We want to create class that has method for sync parallel and async parallel execution of methods.

Now we show, how to code the 8 byte method to 4 byte adress of method.

function TAxThread.NotifyEventToPointer( Proc : TNotifyEvent ) : Longint;
var
  Method : TMethod absolute Proc;
  PMethod : ^TMethod;
begin
  New( PMethod );
PMethod^ :
= Method; Result := Longint( PMethod ); end; procedure TAxThread.AsyncExecuteParallel( Proc : TNotifyEvent; ParamSender : TObject ); begin InterlockedIncrement( FMethodsToExecute ); PostMessageParallel( CM_EXECPROC_WORK_TRHREAD, NotifyEventToPointer( Proc ), Longint( ParamSender ) ); end; procedure TAxThread.ExecProcedure( var Message : TMessage ); var PMethod : ^TMethod; Method : TMethod; Event : TNotifyEvent absolute Method; begin PMethod := Pointer( message.WParam ); Method := PMethod^;
Event( TObject(
message.LParam ) );
Dispose( PMethod );
if FThreadID = GetCurrentThreadId then InterlockedDecrement( FMethodsToExecute ); end;

 

NotifyEventToPointer is method for copying method to memory block.

Result of this method is adress of memory block.

Method ExeProcedure takes WParam of message, convert it back to method

and call decoded method with parametr defined by LParam of message.

Thread loop

We have to define thread message loop.

Delphi define basic thread class TThread.

This class has virtual method execute.

Defined class TAxThread is inherited from TThread.

Method TAxThread.Execute is overrided; 

procedure TAxThread.Execute;
var
  Msg : TMsg;
  Done : Boolean;
begin
  CreateHandleParallel;
  FThreadID := GetCurrentThreadId;
  while not Terminated do
  begin
    if Done then
    begin
      FIdleData := nil;
      WaitMessage;
    end;
    while ProcessMessage( Msg ) do { loop };
    Idle( FIdleData, Done );
  end;
end;

 At first the class has to create handle (CreateHandleParallel).

Loop while waits for messages and handles incomming messages (ProcessMessage).

function TAxThread.ProcessMessage( var Msg : TMsg ) : Boolean;
begin
  Result := False;
  if PeekMessage( Msg, 0, 0, 0, PM_REMOVE ) thenbegin
    Result := True;
    if Msg.Message <> WM_QUIT then
    begin
      TranslateMessage( Msg );
      DispatchMessage( Msg );
    end
    else
      Terminate;
  end;
end;

Calling DispatchMessage dispatch current message to objects for execution.

If incomming method is CM_EXECUTE (defined in Delphi), then method ExecProcedure is called.

Sync and async execution

All parallel processing is sended to execution by message.

If we use for sending the API function SendMessage then the execution is synchronized

(actual thread is suspend, the context is switched, message is immediately handled and control is returned to sending thread).

The API function PostMessage puts the message to message queue and continue in execution.

When the message is peek from queue, is handled and method is executed.

// Asynchro execute on parallel thread
procedure TAxThread.AsyncExecuteParallel( Proc : TNotifyEvent;
  ParamSender : TObject );
begin
  InterlockedIncrement( FMethodsToExecute );
  PostMessageParallel( CM_EXECPROC, NotifyEventToPointer( Proc ),
    Longint( ParamSender ) );
end;

// Synchro execute on parallel thread
procedure TAxThread.SyncExecuteParallel( Proc : TNotifyEvent;
  ParamSender : TObject );
begin
  InterlockedIncrement( FMethodsToExecute );
  SendMessageParallel( CM_EXECPROC, NotifyEventToPointer( Proc ),
    Longint( ParamSender ) );
end;

Introduced method AsyncExecuteParallel is used for async parallel execution

(execution on selected thread) of method Proc with parameter ParamSender.

 

 

Method SyncExecuteParallel runs method Proc with parameter ParamSender synchronously (waits for execution).

 

The thread defines method for execution on main thread (AsyncExecuteMain, SyncExecuteMain).

Using

For example of using define class

type
  TMainForm = class( TForm )
    btnRandomize : TButton;
    imgResult : TImage;
    pbProgress : TProgressBar;
    btnMulti : TButton;
    procedure FormCreate( Sender : TObject );
    procedure FormDestroy( Sender : TObject );
    procedure btnRandomizeClick( Sender : TObject );
    procedure btnMultiClick( Sender : TObject );
  private
    { Private declarations }
    FExecutingThread : TAxThread;
  public
    { Public declarations }
    procedure RandomizeBMP( Data : TObject );
    procedure DoUpdate( Data : TObject );
    procedure Progress( Data : TObject );
  end;

Private variable FExecutingThread is thread on witch the metods will be executed.

There are three public method in form of TNotifyEvent (can be executed on selected thread).

procedure TMainForm.btnRandomizeClick( Sender : TObject );
var
  PomBMP : TBitmap;
begin
  if FExecutingThread = nil then
    FExecutingThread := TAxThread.Create;
  PomBMP := TBitmap.Create;
  PomBMP.Width := 200;
  PomBMP.Height := 200;
  PomBMP.PixelFormat := pf24bit;
  FExecutingThread.AsyncExecuteParallel( RandomizeBMP, PomBMP );
end;

When user clicked on button then method btnRandomizeClick is called.

Method calls AsyncExecuteParallel.

Main thread continue in responsing to user interaction.

When the thread contrext is switched, FExecutingThread begins execute then method RandomizeBMP.

procedure TMainForm.RandomizeBMP( Data : TObject );
var
  CurrentThread : TAxThread;
  PomBMP : TBitmap;
  I : Longint;
  X : Longint;
  Y : Longint;
  Color : Longint;
begin
  if not( Data is TBitmap ) then
    Exit;
  PomBMP := Data as TBitmap;
  CurrentThread := TAxThread.GetCurrentThread;
  I := 0;
  try
    PomBMP.Canvas.Lock;
    while not CurrentThread.Terminated do
    begin
      Inc( I );
      if I > MaxPoints then
        Break;
      X := Random( 200 );
      Y := Random( 200 );
      Color := Random( 256 ) * 256 * 256 + Random( 256 ) * 256 + Random( 256 );
      if ( I mod ( MaxPoints div 100 ) ) = 0 then
        CurrentThread.SyncExecuteMain( Progress, TObject( I ) );
      // slow for demonstration
      PomBMP.Canvas.Pixels[ X, Y ] := Color;
    end;
  finally
    PomBMP.Canvas.Unlock;
    CurrentThread.AsyncExecuteMain( DoUpdate, PomBMP );
  end;
end;

Method RandomizeBMP decode parameter as Bitmap and fills it with some random points.

When randomize is finished, the thread notify main thread (CurrentThread.AsyncExecuteMain).


Conclusion


We present implementation of thread class for async (sync) execution.

The designed library has very effective method for developing of parallel computing.

 

  1 unit AxThreads2;
  2 
  3 interface
  4 
  5 uses
  6   Windows, Classes, SysUtils, Controls, Forms, Messages;
  7 
  8 const
  9   CM_EXECPROC = $8FFF;
 10 
 11 type
 12   TIdleEvent = procedure( var Data : TObject; var Done : Boolean ) of object;
 13 
 14   TAxThread = class( TThread )
 15   private
 16     FThreadID : Longint;
 17     FOnIdle : TIdleEvent;
 18     FIdleData : TObject;
 19     FWndHandleParallel : LongWord;
 20     FWndHandleMain : LongWord;
 21 
 22     FMethodsToExecute : Longint;
 23     procedure SetOnIdle( const Value : TIdleEvent );
 24   protected
 25     procedure CreateHandleParallel;
 26     procedure DestroyHandleParallel;
 27     procedure CreateHandleMain;
 28     procedure DestroyHandleMain;
 29 
 30     procedure MessageProcedure( var Message : TMessage ); virtual;
 31 
 32     procedure ExecProcedure( var Message : TMessage ); message CM_EXECPROC;
 33     procedure Execute; override;
 34     procedure Idle( var Data : TObject; var Done : Boolean ); virtual;
 35 
 36     function ProcessMessage( var Msg : TMsg ) : Boolean;
 37 
 38     function NotifyEventToPointer( Proc : TNotifyEvent ) : Longint;
 39   public
 40     constructor Create;
 41     destructor Destroy; override;
 42 
 43     // Posts a null message to parallel thread
 44     procedure WakeUp;
 45 
 46     class function GetCurrentThread : TAxThread;
 47     // sends a message to main thread
 48     function SendMessageMain( Msg, WParam, LParam : LongWord ) : Longint;
 49     // posts a message to main thread
 50     function PostMessageMain( Msg, WParam, LParam : LongWord ) : LongBool;
 51 
 52     // sends a message to parallel thread
 53     function SendMessageParallel( Msg, WParam, LParam : LongWord ) : Longint;
 54     // posts a message to parallel thread
 55     function PostMessageParallel( Msg, WParam, LParam : LongWord ) : LongBool;
 56 
 57     // for compatibility
 58     function SendMessage( Msg, WParam, LParam : LongWord ) : Longint;
 59     function PostMessage( Msg, WParam, LParam : LongWord ) : LongBool;
 60 
 61     // Synchro execute on parallel thread
 62     procedure SyncExecuteParallel( Proc : TNotifyEvent; ParamSender : TObject );
 63     // Asynchro execute on parallel thread
 64     procedure AsyncExecuteParallel( Proc : TNotifyEvent;
 65       ParamSender : TObject );
 66 
 67     // Synchro execute on main thread
 68     procedure SyncExecuteMain( Proc : TNotifyEvent; ParamSender : TObject );
 69     // Asynchro execute on main thread
 70     procedure AsyncExecuteMain( Proc : TNotifyEvent; ParamSender : TObject );
 71 
 72     // for compatibility
 73     procedure SyncExecute( Proc : TNotifyEvent; ParamSender : TObject );
 74     procedure AsyncExecute( Proc : TNotifyEvent; ParamSender : TObject );
 75 
 76     // handle for main thread
 77     property WndHandleMain : LongWord read FWndHandleMain;
 78 
 79     // handle for parallel thread
 80     property WndHandleParallel : LongWord read FWndHandleParallel;
 81 
 82     // idle event for parallel thread
 83     property OnIdle : TIdleEvent read FOnIdle write SetOnIdle;
 84 
 85     property MethodsToExecute : Longint read FMethodsToExecute;
 86 
 87     property Terminated;
 88   end;
 89 
 90 implementation
 91 
 92 { TAxThread }
 93 var
 94   AxThreads : TThreadList;
 95 
 96 procedure TAxThread.CreateHandleParallel;
 97 begin
 98   FWndHandleParallel := AllocateHWnd( MessageProcedure );
 99 end;
100 
101 procedure TAxThread.MessageProcedure( var Message : TMessage );
102 begin
103   Dispatch( message );
104 end;
105 
106 procedure TAxThread.DestroyHandleParallel;
107 begin
108   DeallocateHWnd( FWndHandleParallel );
109   FWndHandleParallel := 0;
110 end;
111 
112 constructor TAxThread.Create;
113 begin
114   // this must by synchonized because of MainThread Context
115   FMethodsToExecute := 0;
116 
117   inherited Create( False );
118   Synchronize( CreateHandleMain );
119 
120   if AxThreads = nil then
121     AxThreads := TThreadList.Create;
122 
123   AxThreads.Add( Self );
124 end;
125 
126 destructor TAxThread.Destroy;
127 begin
128   Terminate;
129   WakeUp;
130   inherited;
131   DestroyHandleParallel;
132   DestroyHandleMain;
133 
134   AxThreads.Remove( Self );
135 end;
136 
137 procedure TAxThread.AsyncExecuteParallel( Proc : TNotifyEvent;
138   ParamSender : TObject );
139 begin
140   InterlockedIncrement( FMethodsToExecute );
141   PostMessageParallel( CM_EXECPROC, NotifyEventToPointer( Proc ),
142     Longint( ParamSender ) );
143 end;
144 
145 procedure TAxThread.SyncExecuteParallel( Proc : TNotifyEvent;
146   ParamSender : TObject );
147 begin
148   InterlockedIncrement( FMethodsToExecute );
149   SendMessageParallel( CM_EXECPROC, NotifyEventToPointer( Proc ),
150     Longint( ParamSender ) );
151 end;
152 
153 procedure TAxThread.ExecProcedure( var Message : TMessage );
154 var
155   PMethod : ^TMethod;
156   Method : TMethod;
157   Event : TNotifyEvent absolute Method;
158 begin
159   PMethod := Pointer( message.WParam );
160 
161   Method := PMethod^;
162   Event( TObject( message.LParam ) );
163 
164   Dispose( PMethod );
165   if FThreadID = GetCurrentThreadId then
166     InterlockedDecrement( FMethodsToExecute );
167 end;
168 
169 procedure TAxThread.Execute;
170 var
171   Msg : TMsg;
172   Done : Boolean;
173 begin
174   CreateHandleParallel;
175   FThreadID := GetCurrentThreadId;
176 
177   while not Terminated do
178   begin
179     if Done then
180     begin
181       FIdleData := nil;
182       WaitMessage;
183     end;
184     while ProcessMessage( Msg ) do { loop };
185     Idle( FIdleData, Done );
186   end;
187 end;
188 
189 procedure TAxThread.CreateHandleMain;
190 begin
191   FWndHandleMain := AllocateHWnd( MessageProcedure );
192 end;
193 
194 procedure TAxThread.DestroyHandleMain;
195 begin
196   DeallocateHWnd( FWndHandleMain );
197   FWndHandleMain := 0;
198 end;
199 
200 procedure TAxThread.AsyncExecuteMain( Proc : TNotifyEvent;
201   ParamSender : TObject );
202 begin
203   PostMessageMain( CM_EXECPROC, NotifyEventToPointer( Proc ),
204     Longint( ParamSender ) );
205 end;
206 
207 procedure TAxThread.SyncExecuteMain( Proc : TNotifyEvent;
208   ParamSender : TObject );
209 begin
210   SendMessageMain( CM_EXECPROC, NotifyEventToPointer( Proc ),
211     Longint( ParamSender ) );
212 end;
213 
214 function TAxThread.PostMessageMain( Msg, WParam, LParam : LongWord ) : LongBool;
215 begin
216   Result := Windows.PostMessage( FWndHandleMain, Msg, WParam, LParam );
217 end;
218 
219 function TAxThread.PostMessageParallel( Msg, WParam, LParam : LongWord )
220   : LongBool;
221 begin
222   while FWndHandleParallel = 0 do
223     SwitchToThread;
224 
225   Result := Windows.PostMessage( FWndHandleParallel, Msg, WParam, LParam );
226 end;
227 
228 function TAxThread.SendMessageMain( Msg, WParam, LParam : LongWord ) : Longint;
229 begin
230   Result := Windows.SendMessage( FWndHandleMain, Msg, WParam, LParam );
231 end;
232 
233 function TAxThread.SendMessageParallel( Msg, WParam, LParam : LongWord )
234   : Longint;
235 begin
236   while FWndHandleParallel = 0 do
237     SwitchToThread;
238 
239   Result := Windows.SendMessage( FWndHandleParallel, Msg, WParam, LParam );
240 end;
241 
242 procedure TAxThread.Idle( var Data : TObject; var Done : Boolean );
243 begin
244   if Assigned( FOnIdle ) then
245     FOnIdle( Data, Done )
246   else
247     Done := True;
248 end;
249 
250 function TAxThread.ProcessMessage( var Msg : TMsg ) : Boolean;
251 begin
252   Result := False;
253   if PeekMessage( Msg, 0, 0, 0, PM_REMOVE ) then
254   begin
255     Result := True;
256     if Msg.Message <> WM_QUIT then
257     begin
258       TranslateMessage( Msg );
259       DispatchMessage( Msg );
260     end
261     else
262       Terminate;
263   end;
264 end;
265 
266 procedure TAxThread.SetOnIdle( const Value : TIdleEvent );
267 begin
268   FOnIdle := Value;
269   SendMessageParallel( 0, 0, 0 );
270 end;
271 
272 procedure TAxThread.AsyncExecute( Proc : TNotifyEvent; ParamSender : TObject );
273 begin
274   AsyncExecuteParallel( Proc, ParamSender );
275 end;
276 
277 procedure TAxThread.SyncExecute( Proc : TNotifyEvent; ParamSender : TObject );
278 begin
279   SyncExecuteParallel( Proc, ParamSender );
280 end;
281 
282 function TAxThread.PostMessage( Msg, WParam, LParam : LongWord ) : LongBool;
283 begin
284   Result := PostMessageParallel( Msg, WParam, LParam );
285 end;
286 
287 function TAxThread.SendMessage( Msg, WParam, LParam : LongWord ) : Longint;
288 begin
289   Result := SendMessageParallel( Msg, WParam, LParam );
290 end;
291 
292 function TAxThread.NotifyEventToPointer( Proc : TNotifyEvent ) : Longint;
293 var
294   Method : TMethod absolute Proc;
295   PMethod : ^TMethod;
296 begin
297   New( PMethod );
298   PMethod^ := Method;
299   Result := Longint( PMethod );
300 end;
301 
302 procedure TAxThread.WakeUp;
303 begin
304   PostMessageParallel( 0, 0, 0 );
305 end;
306 
307 class function TAxThread.GetCurrentThread : TAxThread;
308 var
309   PomList : TList;
310   PomIndex : Longint;
311   ThreadID : Longint;
312 begin
313   Result := nil;
314   if AxThreads = nil then
315     Exit;
316 
317   ThreadID := GetCurrentThreadId;
318   PomList := AxThreads.LockList;
319   try
320     for PomIndex := 0 to PomList.Count - 1 do
321     begin
322       Result := TAxThread( PomList[ PomIndex ] );
323       if Result.FThreadID = ThreadID then
324         Break;
325       Result := nil;
326     end;
327   finally
328     AxThreads.UnlockList;
329   end;
330 end;
331 
332 end.

 

posted @ 2014-10-08 20:37  IAmAProgrammer  阅读(646)  评论(0编辑  收藏  举报