Synchronization in Delphi TThread class

VCL sources is a very interesting reading, if you have a time for it. Many Delphi programmers (like me) use VCL TThread class as a simple wrapper for WinAPI kernel thread object, avoiding to use Synchronize and Queue methods by using a custom message processing instead. Still it is worthwhile to understand the inner workings of VCL thread synchronization.

Disclaimer: I am currently using Delphi 2009, so some details of the next study may be different for the other Delphi (VCL) versions.

TThread class affords two ways for a background (“worker”) thread to execute a code in context of the main (GUI) thread. You can use either one of the Synchronize method overloads which force the worker thread into a wait state until the code is executed, or else you can use one of the Queue method overloads that just put the code into the main thread’s synchronization queue and allows a worker thread to continue. The definitions of the Synchronize and Queue methods in TThread class are:

type
  TThreadMethod = procedure of object;
  TThreadProcedure = reference to procedure;

procedure Synchronize(AMethod: TThreadMethod); overload;
procedure Synchronize(AThreadProc: TThreadProcedure); overload;
class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;
class procedure Synchronize(AThread: TThread; AThreadProc: TThreadProcedure); overload;

procedure Queue(AMethod: TThreadMethod); overload;
procedure Queue(AThreadProc: TThreadProcedure); overload;
class procedure Queue(AThread: TThread; AMethod: TThreadMethod); overload;
class procedure Queue(AThread: TThread; AThreadProc: TThreadProcedure); overload;

We see that Synchronize and Queue methods can be used either with ordinary methods (TThreadMethod) or anonymous methods (TThreadProcedure).

Both Synchronize and Queue methods internally call a private Synchronize method overload, which is examined in detail next.

class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord; QueueEvent: Boolean = False); overload;

The first argument is a pointer to TSynchronizeRecord structure (see definition below) which in turn contains a pointer to a code to be executed; a code pointer can be either ordinary method pointer (TThreadMethod) or anonymous method’s reference (TThreadProcedure)

type
  PSynchronizeRecord = ^TSynchronizeRecord;
  TSynchronizeRecord = record
    FThread: TObject;
    FMethod: TThreadMethod;
    FProcedure: TThreadProcedure;
    FSynchronizeException: TObject;
  end;

  TSyncProc = record
    SyncRec: PSynchronizeRecord;
    Queued: Boolean;
    Signal: THandle;
  end;

The second argument (QueueEvent) is False for Synchronize overloads and True for Queue overloads.

Now back to the TThread.Synchronize code:

var
  SyncProc: TSyncProc;
  SyncProcPtr: PSyncProc;
begin
  if GetCurrentThreadID = MainThreadID then begin
    if Assigned(ASyncRec.FMethod) then
      ASyncRec.FMethod()
    else if Assigned(ASyncRec.FProcedure) then
      ASyncRec.FProcedure();
  end

The above is a kind of “fool protection”. If Synchronize (or Queue) is called from the main thread, the correspondent code is just executed. The rest of the method’s code is dealing with the worker’s thread calls:

  else begin
     if QueueEvent then
      New(SyncProcPtr)
    else
      SyncProcPtr := @SyncProc;

For Synchronize calls we use stack variable to store TSyncProc structure; we can’t use stack for asynchronous Queue calls, so we allocate a new TSyncProc variable on the heap.

    if not QueueEvent then
      SyncProcPtr.Signal := CreateEvent(nil, True, False, nil)
    else
      SyncProcPtr.Signal := 0;

For Synchronize calls we need a signaling event to wake up the worker thread after the main thread have finished executing the synchronized code; for the Queue calls we does not interfere into thread scheduling, so we need not a signaling event.

      try
        EnterCriticalSection(ThreadLock);
      try
        SyncProcPtr.Queued := QueueEvent;
        if SyncList = nil then
          SyncList := TList.Create;
        SyncProcPtr.SyncRec := ASyncRec;
        SyncList.Add(SyncProcPtr);
        SetEvent(SyncEvent);
        if Assigned(WakeMainThread) then
          WakeMainThread(SyncProcPtr.SyncRec.FThread);

We are preparing an information for the main thread about the code to execute. I will discuss how (and where) the main thread finds out that it has some worker code to execute a little later.

        if not QueueEvent then begin
          LeaveCriticalSection(ThreadLock);
          try
            WaitForSingleObject(SyncProcPtr.Signal, INFINITE);
          finally
            EnterCriticalSection(ThreadLock);
          end;
        end;

A very interesting code fragment (executed only for Synchronize calls). We release the ThreadLock critical section (to enable other threads to execute Synchronize or Queue calls), wait the main thread to execute the code and enter the ThreadLock again.
The above code fragment is exactly the job for a condition variable. Condition variable is a synchronization primitive first introduced in Windows Vista (in Windows part of the world; it probably always existed in Unix/Linux). Have VCL supported only Vista and above Windows versions, the above code fragment could be replaced like this:

        if not QueueEvent then begin
          SleepConditionVariableCS(CondVar, ThreadLock, INFINITE);
        end;

(well, not so simple; we should initialize the CondVar condition variable before we can use it. See msdn for details).

The use of the conditional variables makes the code more effective (thread enters a wait state and releases the specified critical section as an atomic operation) and more readable.

      finally
        LeaveCriticalSection(ThreadLock);
      end;
    finally
      if not QueueEvent then
        CloseHandle(SyncProcPtr.Signal);
    end;
    if not QueueEvent and Assigned(ASyncRec.FSynchronizeException) then
      raise ASyncRec.FSynchronizeException;
  end;
end;

That is all. Both Synchronize and Queue calls prepare a TSyncProc structure containing a pointer to the code to be executed by the main thread, insert the structure into the global SyncList list, and with Synchronize call a worker thread also enters a wait state until the main thread sets a signaling event in the TSyncProc structure.

Now about how the main thread finds out that the worker threads have prepared TSyncProc structures for him. The answer is WakeMainThread global variable defined in Classes.pas:

var
  WakeMainThread: TNotifyEvent = nil;

in the above Synchronize procedure we have

      if Assigned(WakeMainThread) then
          WakeMainThread(SyncProcPtr.SyncRec.FThread);

The WakeMainThread is assigned during the application initialization by the following code:

procedure TApplication.WakeMainThread(Sender: TObject);
begin
  PostMessage(Handle, WM_NULL, 0, 0);
end;

and WM_NULL message is processed in application window procedure as follows:

procedure TApplication.WndProc(var Message: TMessage);
[..]
    with Message do
      case Msg of
[..]

        WM_NULL:
          CheckSynchronize;
[..]

We see that every Synchronize or Queue method call posts WM_NULL message to the hidden application window; the WM_NULL message is processed by calling CheckSynchronize procedure. CheckSynchronize procedure scans the global SyncList list of the TSyncProc structures, executes the code pointed by them and, for Synchronize calls only, sets a signaling event in the TSyncProc structures after executing the code to wake up a worker thread.

What is the resume of the above analysis? Both Synchronize and Queue methods internally use window message processing (in the hidden application window), and in fact there is a little difference with custom message processing here. Due to design reasons a custom message processing is preferable when designing multithreaded components (with their own window procedures, created for example by Classes.AllocateHWnd function). Usually there is no practical reason to use custom message processing when designing multithreaded applications – Synchronize and Queue methods of the TThread class are just as good.

Advertisements

4 thoughts on “Synchronization in Delphi TThread class

  1. Custom Messages had been useful until the introduction of TThreadProcedure.
    Before its introduction if you want to pass additional parameters to the Syncronized Method you have to introduce additional field/properties in your TThread descendant classes or to use a custom message in order to use WPARAM and LPARAM.

    The use of TThreadProcedure allows the use anonymous method that can capture parameters as anonymous method’s local variable.

    The handling of the main thead’ waking up the has changed since delphi 7.
    In delphi 7, and maybe in other version, the CheckSynchronize is called in the Application.ProcesseMessages function. It means that it is called only if your thread is hosted in an applicaton that has a message pump implemented by a delphi’s TApplication.

    This does not work if you make an ActiveX that has secondary threads implemented using TThread that is hosted in a “foreign ” host (Es VB app).

    The new implementation does not suffer of this problem.

    Regards,
    Stefano Moratto

  2. There is no need to write a custom messaging solution from scratch, you can build one using the TThread.Queue method. I have done this myself such that I can “post” any data structures (simple types, compount types, objects, etc) from background threads to the main thread. TThread.Queue is one of those undocumented hidden gems that was introduced in Delphi 2005 that I cannot do without.

  3. Hello!

    I’m gettin’ a issue and i’m tring to solve it using Threads.

    I need to create parallel loops and wait for then, but when i wait for a thread and i create another thread and wait for this one, then they enter on a queue, so while not the last thread is terminated, them the behind threads don’t terminate… Is something like a ShowModal with multwindows.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s