Synchronization in Delphi TThread class : Synchronize, Queue

http://embarcadero.newsgroups.archived.at/public.delphi.rtl/201112/1112035763.html

> Hi,
>
> What is the difference between these two definitions:
>
>  TThreadMethod = procedure of object;
>  TThreadProcedure = reference to procedure;
>
> Please can you try to explain simply :)

Well, it is not exactly a simple subject ;-)

TThreadMethod declares a method pointer type.

Internally, a method pointer is represented as a record of two pointers (there is a type TMethod for that).

TMethod = record
    code: pointer;
    data: pointer
  end;

If you declare a variable of a method pointer type you can only assign
it a method of an object. Wayne's example is wrong here since it uses
the @ operator. You do not use that for assigning a method to a method
pointer variable.

    TMyClass = class
    private
        MyMethod: ThreadMethod;
        procedure Foo;

  MyMethod := Foo;

On this assignment the compiler internally executes

  TMethod(MyMethod).Code := @TMyClass.Foo;
  TMethod(MyMethod).Data := self;  // actually the address of the object Foo belongs to

Calling the method through the method pointer will pass the Data value
as the hidden Self parameter to Foo. So you can only use a method
pointer safely as long as the object the pointer refers to still exists.

TThreadProcedure = reference to procedure;

This declares a new-fangled thing called an anonymous method, but it is
in fact more generally useful than the use  Wayne showed. To a variable
of type TThreadProcedure you could assign not only a nameless procedure
constructed on the fly, but also an existing method of some object, or
even a standalone procedure. The compiler does a lot more work behind
the scene for these types. It creates a whole class with a matching
interface type, creates an instance, stores any local variables the
anonymous method refers to into this instance, implements the anonymous
method as a method of this class, calls it, cleans up afterwards.
"reference to x" types are not simple types, unlike method or function
pointer types. They have considerable overhead in terms of code size
and execution, but are a lot more flexible in turn.

Anonymous methods are Delphi's implementation of a computer science
concept called a closure (see
http://en.wikipedia.org/wiki/Closure_%28computer_science%29 )

-- 

 

http://www.cnblogs.com/del/archive/2011/05/18/2049913.html

 

先看一个非多线程的例子, 代码执行时不能进行其它操作(譬如拖动窗体):

{自定义方法: 在窗体上绘制...}
procedure MyMethod;
var
  i: Integer;
begin
  for i := 0 to 500000 do
  begin
    Form1.Canvas.Lock;
    Form1.Canvas.TextOut(10, 10, IntToStr(i));
    Form1.Canvas.Unlock;
  end;
end;

{调用上面的自定义方法}
procedure TForm1.Button1Click(Sender: TObject);
begin
  MyMethod;
end;

修改为多线程(只修改一行代码):

procedure MyMethod;
var
  i: Integer;
begin
  for i := 0 to 500000 do
  begin
    Form1.Canvas.Lock;
    Form1.Canvas.TextOut(10, 10, IntToStr(i));
    Form1.Canvas.Unlock;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TThread.CreateAnonymousThread(MyMethod).Start; //!!!
end;

 

 代码分析:

1、TThread 现在增加了许多 class 方法(直接通过类名调用的方法), TThread.CreateAnonymousThread() 就是比较有用的一个.

2、顾名思义, CreateAnonymousThread 是建立匿名线程对象, 它的参数是我们需要在线程中执行的方法.

3、但 CreateAnonymousThread 建立线程后是挂起的, 需要手动运行它; 后面的 Start 方法就是用来唤醒线程的.

4、(以前)唤醒线程还可以使用 Resume 方法或 Suspended 属性(Suspended := False;); 但它们即将被废弃了, 现在应使用 Start 来启动线程.

CreateAnonymousThread 的参数类型 TProc 是匿名方法(reference), 所以代码可以简写为:

procedure TForm1.Button1Click(Sender: TObject);
begin
  TThread.CreateAnonymousThread( //直接写入方法体
    procedure
    var
      i: Integer;
    begin
      for i := 0 to 500000 do
      begin
        Canvas.Lock;
        Canvas.TextOut(10, 10, IntToStr(i));
        Canvas.Unlock;
      end;
    end //此处无分号
  ).Start;
end;

延时执行:

var
  myThread: TThread;

procedure TForm1.FormCreate(Sender: TObject);
begin
  myThread := TThread.CreateAnonymousThread(
    procedure
    var
      i: Integer;
    begin
      for i := 0 to 500000 do
      begin
        Canvas.Lock;
        Canvas.TextOut(10, 10, IntToStr(i));
        Canvas.Unlock;
      end;
    end
  );
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  myThread.Start;
end;

 

http://www.uweraabe.de/Blog/2011/01/30/synchronize-and-queue-with-parameters/

Synchronize and Queue with Parameters

In Delphi’s TThread class there is Synchronize to call a method in the context of the GUI thread.

This is necessary in case, for example, you want to update a progressbar or a status label in a form, because the VCL is not thread safe.

While Synchronize is a blocking method (i.e. the thread code continues when the GUI thread has finished the method call),

recent versions of Delphi introduced a non-blocking Queue method.

The drawback of using Synchronize is that it is very cumbersome to handle parameters during such a call.

The standard solution is to use fields in the TThread descendant holding the parameters during Synchronize,

but this won’t work properly with Queue. 

As Queue is non-blocking there might be multiple calls to Queue waiting to be processed,

which often cannot share the same field.

Worse, using Queue access to these fields have to be thread safe, too.

The queuing thread may just set this field for the next call to Queuewhile a previous Queue call is executed

in the main thread just using this field.

As this problem doesn’t exist with Synchronize, switching

from Synchronize to Queue just has to be done more careful.

One way to deal with parameters for Synchronize is the use of Anonymous Methods.

There is an overloaded version of Synchronize and Queue taking a TThreadProcedure as parameter:

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

procedure Queue(AMethod: TThreadMethod); overload;
procedure Synchronize(AMethod: TThreadMethod); overload;
procedure Queue(AThreadProc: TThreadProcedure); overload;
procedure Synchronize(AThreadProc: TThreadProcedure); overload;

 

A simple example for a parametrized call to Synchronize could be like this: 

 

type
  TMyProgressEvent = procedure(PercentComplete: Integer) of object;

  TMyThread = class(TThread)
  private
    FOnMyProgress: TMyProgressEvent;
  protected
    procedure CallMyProgress(PercentComplete: Integer);
    procedure Execute; override;
  public
    property OnMyProgress: TMyProgressEvent
      read FOnMyProgress
      write FOnMyProgress;
  end;

procedure TMyThread.CallMyProgress(PercentComplete: Integer);
begin
  if GetCurrentThreadId = MainThreadID then begin
    if Assigned(FOnMyProgress) then
      FOnMyProgress(PercentComplete);
  end
  else begin
    Synchronize(
      procedure
      begin
        CallMyProgress(PercentComplete);
      end);
  end;
end;

procedure TMyThread.Execute;
var
  I: Integer;
begin
  for I := 0 to 100 do begin
    CallMyProgress(I);
    Sleep(10);
  end;
end;

 

The magic is that the Anonymous Method captures the variable PercentComplete.

Thus it will be totally safe to use Queue instead of Synchronize here.

I’m using a little trick here to ensure that the critical code is called inside the GUI thread without having a separate method.

The ID of the GUI thread is stored in MainThreadID, so I just check if the result of the function GetCurrentThreadID matches

this value and call Synchronize otherwise.

One note using Queue:

when the thread is freed, all pending Queue calls are removed.

So if your code depends on all these calls being handled in the GUI thread

you have to make sure that the thread instance lives at least that long.

 

http://sergworks.wordpress.com/2010/09/14/synchronization-in-delphi-tthread-class/

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.

 

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

 

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.

 

posted @ 2014-10-02 18:30  IAmAProgrammer  阅读(4815)  评论(0编辑  收藏  举报