Mega Code Archive

 
Categories / Delphi / Ide Indy
 

Delphi 6, 7 threads synchronization in ActiveX controls

Title: Delphi 6, 7 threads synchronization in ActiveX controls Question: Abstract: This article describes how to improve Delphi 6, 7 threads synchronization in ActiveX controls Answer: Delphi 6, 7 threads synchronization in ActiveX controls ActiveX controls Abstract Starting from Delphi 6 the VCL synchronization mechanism underwent big changes. It does not use anymore any hidden window handles in order to organize method calls between Delphi threads. All these changes were necessary for VCL library to be compatible with Kylix version of VCL. However there are situations when you have to perform some additional manipulations to make your code work properly. Please note that Delphi 6 synchronization model will not work when threads used inside of dll or ActiveX control placed onto the Web form. Such behavior is due to using of the CheckSynchronize function for processing of the VCL synchronization queue. If you are writing an exe application this function is called automatically, but if you are developing an ActiveX library there are no any mechanism to do it. Another example, if you are developing some ActiveX control which can be used inside web page. The web page can be opened using Microsoft Internet Explorer (IE) in more than one browser window simultaneously. In contrast to Borland VCL, IE has different working threads per each browser window therefore Delphi synchronization model also will not work inside such multiple windows. All information described above also applies to working with services. Call CheckSynchronize in dll If you want to use the VCL synchronization feature, you should provide the CheckSynchronize calls as it needs. The most easiest way to provide your application with asynchronous mode is to assign the WakeMainThread callback routine. This callback is fired when downloader's thread is about to synchronize with the main thread. Next, within the WakeMainThread handler we post a message to the active form's window. Finally the message handler calls the CheckSynchronize to process threads synchronization. The sample below demonstrates using of WakeMainThread routine in a Delphi ActiveX Form: const WM_CLSYNCHRONIZE = WM_USER + 1; ... type TActiveFormX = class(TActiveForm, IActiveFormX) procedure WMclSynchronize(var Message: TMessage); message WM_CLSYNCHRONIZE; ... procedure TActiveFormX.ActiveFormCreate(Sender: TObject); begin Classes.WakeMainThread := DoOnWakeMainThread; end; procedure TActiveFormX.DoOnWakeMainThread(Sender: TObject); begin PostMessage(Self.Handle, WM_CLSYNCHRONIZE, 0, 0); end; procedure TActiveFormX.WMclSynchronize(var Message: TMessage); begin Classes.CheckSynchronize(); end; When creating a new instance of the ActiveX Form, the WakeMainThread event handler is initialized. When the main thread is waked up, the user-defined message is posted to the Form handle and finally CheckSynchronize is called. Back to the hidden window. The method explained above uses window handles to provide the ability of processing the synchronization queue. Such approach looks not so effective. So we would rather use window handles only instead of using an additional synchronization engine. But neither CheckSynchronize nor Delphi 5 window based synchronization model works if you need to wait for thread completion from the base thread which is not the main application thread. Lets go ahead and take a close look at TThread class in Delphi 5. When new instance of TThread is created, the AddThread procedure is called. It creates the hidden window during it's first call. All other calls are expected to be from the same thread as the first call. Now please take a look at WaitFor method. There is a condition which defines when thread synchronization engine should work - only when you invoke this method from the inside of the main application thread. If not, the message queue will not be processed. function TThread.WaitFor: LongWord; begin ... if GetCurrentThreadID = MainThreadID then while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_SENDMESSAGE) = WAIT_OBJECT_0 + 1 do PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) else WaitForSingleObject(H, INFINITE); ... end; As a result, you can not use these methods when developing Windows services and ActiveX controls designed for Web Forms. Adding true multi-threading synchronization to the VCL The suggested solution relies on using of the separate window handles per each base thread (with which another threads need to be synchronized). We will need for some generic class which can handle all synchronization windows being used: TclSynchronizerManager = class ... function FindSyncInfo(ASyncBaseThreadID: LongWord): TclSyncInfo; procedure AddThread(ASynchronizer: TclThreadSynchronizer); procedure RemoveThread(ASynchronizer: TclThreadSynchronizer); procedure Synchronize(ASynchronizer: TclThreadSynchronizer); ... end; TclThreadSynchronizer = class ... property SyncBaseThreadID: LongWord read FSyncBaseThreadID; ... end; Each TThread descendant must have its own TclThreadSynchronizer object. When a new TclThreadSynchronizer instance is created, the TclSynchronizerManager.AddThread is called and if there is no windows handle exists for the specified SyncBaseThreadID, new window is created. The rest of synchronization is similar to one which can be found in Delphi 5 except for some minor changes in ThreadWndProc and Synchronize methods. Modified they both must work with corresponding TclThreadSynchronizer instead of using one single hidden window: procedure TclSynchronizerManager.Synchronize( ASynchronizer: TclThreadSynchronizer); begin SendMessage(FindSyncInfo(ASynchronizer.SyncBaseThreadID).FThreadWindow, CM_EXECPROC, 0, Longint(ASynchronizer)); end; Please note that you can not use TThread.WaitFor method to wait for the thread completion. You must use your own one which looks like following: function TThreadEx.Wait: LongWord; begin ... if GetCurrentThreadID = FSynchronizer.SyncBaseThreadID then while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_SENDMESSAGE) = WAIT_OBJECT_0 + 1 do PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) else WaitForSingleObject(H, INFINITE); ... end; Finally to prevent invalid handle exceptions, the Wait method can be modified using the DuplicateHandle routine: procedure TThreadEx.Wait; var Msg: TMsg; H: THandle; begin DuplicateHandle(GetCurrentProcess(), Handle, GetCurrentProcess(), @H, 0, False, DUPLICATE_SAME_ACCESS); try if GetCurrentThreadID = FSynchronizer.SyncBaseThreadID then begin while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_SENDMESSAGE) = WAIT_OBJECT_0 + 1 do begin while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin DispatchMessage(Msg); end; end; end else begin WaitForSingleObject(H, INFINITE); end; finally CloseHandle(H); end; end;