zoukankan      html  css  js  c++  java
  • 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.

  • 相关阅读:
    软件测试的常阅博客
    使用Silverlight操作ASPNETDB数据库
    在Silverlight中实现跨域访问
    部署Silverlight应用时遇到的问题
    如何在WPF和Silverlight中取得DataTemplate中的命名元素
    [转] Silverlight Navigation(多页面切换、传值)
    如何让Button点击后不得focus
    VS2010无法连接到SQlServer2008 Database file
    常用的gulp插件
    Android通过http协议POST传输方式
  • 原文地址:https://www.cnblogs.com/shangdawei/p/4004323.html
Copyright © 2011-2022 走看看