• DLL ActiveForm 线程同步问题


    本文试着从分析Synchronize同步执行的实现机制入手,来解决DLL/ActiveForm中线程同步的问题。
      线程中进行同步时调用的Synchronize函数,仅仅是把调用调用线程、调用方法地址、异常对象封装在一个同步结构中,然后调用处理同步结构的类方法Synchronize。
      procedure TThread.Synchronize(Method: TThreadMethod);
      begin
       FSynchronize.FThread := Self;
       FSynchronize.FSynchronizeException := nil;
       FSynchronize.FMethod := Method;
       Synchronize(@FSynchronize);
      end;
      
      class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);
      var
       SyncProc: TSyncProc;
      begin
       if GetCurrentThreadID = MainThreadID then // 如果是主线程,当然也就不需要同步处理了,直接执行即可
       ASyncRec.FMethod
       else
       begin
       SyncProc.Signal := CreateEvent(nil, True, False, nil); // 创建一个未命名事件,用于主线程执行同步的过程结束时通知等待线程
       try
       EnterCriticalSection(ThreadLock); // 进入ThreadLock关键区,用于保护对Classes单元的全局变量SyncList的访问和进行一些调用的同步
       try
       if SyncList = nil then
       SyncList := TList.Create;
       SyncProc.SyncRec := ASyncRec;
       SyncList.Add(@SyncProc); // 把要同步的结构添加到列表SyncList中,等待主线程取出执行同步过程代码
       SignalSyncEvent; // 调用SetEvent(SyncEvent)使同步事件SyncEvent处于信号状态,主要用在正确处理线程结束时
       if Assigned(WakeMainThread) then
       WakeMainThread(SyncProc.SyncRec.FThread); // 关键!Classes.WakeMainThread就是Application.WakeMainThread,通过PostMessage发个WM_NULL空消息,让主线程醒来
       LeaveCriticalSection(ThreadLock); // 离开关键区,因为主线程调用的过程CheckSynchronize中会在取出SyncList列表同步结构前先进入关键区的
       try
       WaitForSingleObject(SyncProc.Signal, INFINITE); // 等待主线程同步调用结束通知事件
       finally
       EnterCriticalSection(ThreadLock);
       end;
       finally
       LeaveCriticalSection(ThreadLock);
       end;
       finally
       CloseHandle(SyncProc.Signal);
       end;
       if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException;
       end;
      end;
      在主线程的消息处理过程WndProc中检索到WM_NULL消息就会调用CheckSynchronize对同步列表SyncList中的同步结构进行逐个处理,直至SyncList中要同步的方法全部被同步调用完毕。而在应用程序空闲时也会调用CheckSynchronize进行同步处理。
      procedure TApplication.WndProc(var Message: TMessage);
      begin
       try
       (略)
       with Message do
       case Msg of
       WM_NULL:
       CheckSynchronize;
       else
       Default;
       end;
       except
       HandleException(Self);
       end;
      end;
      procedure TApplication.Idle(const Msg: TMsg);
      begin
       (略)
       if (GetCurrentThreadID = MainThreadID) and CheckSynchronize then
       Done := False;
       if Done then WaitMessage;
      end; 
      再看一下Delphi封装的实际线程执行体函数:
      function ThreadProc(Thread: TThread): Integer;
      var
       FreeThread: Boolean;
      begin
       try
       if not Thread.Terminated then
       try
       Thread.Execute;
       except
       Thread.FFatalException := AcquireExceptionObject;
       end;
       finally
       FreeThread := Thread.FFreeOnTerminate; // 是否线程执行结束后自动释放Thread对象实例?
       Result := Thread.FReturnValue;
       Thread.DoTerminate; // 这里调用Synchronize(CallOnTerminate),也就是说OnTerminate事件代码实际上是在主线程中同步执行的
       Thread.FFinished := True;
       SignalSyncEvent; // 使同步事件SyncEvent处于信号状态,使线程Destory调用WaitFor时如果是主线程,可以进行必要的同步处理
       if FreeThread then Thread.Free; // 注意:线程Destroy部分的代码是在线程执行体中执行的
       EndThread(Result);
       end;
      end;
      
      到这里我们基本清楚了Synchronize同步的实现机制:
      线程将同步线程、方法封装成同步结构,添加到同步列表SyncList中,然后发送WM_NULL消息唤醒主线程,然后调用WaitForSingleObject挂起等待主线程处理。主线程处理WM_NULL消息或在空闲时调用CheckSynchronize,从同步列表SyncList取出同步结构,在主线程中调用执行同步方法,执行完毕通过同步结构中的未命名事件句柄通知等待线程。等待线程收到事件通知后醒来,然后继续执行。
      
      以上是对通常的应用程序中的线程Synchronize同步实现机制的分析,而对于DLL中的线程在用Synchronize进行同步时又有它的特殊性。
      DLL中的全局变量是每个DLL都复制一份的,各个DLL之间以及DLL与主程序之间不能直接进行数据共享。这也就是说主程序的SyncEvent、SyncList、ThreadLock与DLL中的SyncEvent、SyncList、ThreadLock变量是不一样的。因此在DLL中的Synchronize同步,使用到的是DLL中的SyncEvent、SyncList、ThreadLock等变量,因此,直接使用Synchronize,主程序的CheckSynchronize就无法对DLL中的线程进行同步调度执行。而DLL中的Application却从来没有运行Run进入消息循环,因此也不能调用CheckSynchronize来处理线程的同步。因此在DLL中就需要主动调用CheckSynchronize函数来对同步列表SyncList进行处理。可以在DLL中专门创建一个窗口,在窗口消息循环中处理WM_NULL消息,调用ChechSynchronize即可。
      while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
       if Msg.message = WM_NULL then
       CheckSynchronize
       else
       begin
       TranslateMessage(Msg);
       DispatchMessage(Msg);
       end;
      
      对ActiveForm中使用的线程调用Synchronize的情况也大体上相同,它的宿主进程是浏览器(如IE),IE一来不是Delphi编的程序,不会在主程序消息循环主调用CheckSynchronize进行同步处理的;二来即使它进行同步处理,也不是采用Delphi的同步实现方法,没有SyncList、SyncEvent、ThreadLock等变量的使用的;另外,ActiveForm由于是个OCX(实际上是种DLL),因此还有如同上述DLL中线程同步的问题,解决办法也可以和上面一样。当然,应该也可以直接在ActiveForm中添加处理WM_NULL消息的处理过程,调用CheckSynchronize进行同步处理,我没有进行试验,具体地,你可以亲自试一下。 

    参考:http://m.blog.csdn.net/blog/fghydx/18699709

  • 相关阅读:
    【结对编程收获】
    【第二次个人作业】结对作业Core第一组:四则运算生成PB16061082+PB16120517
    【第七周读书笔记】读《计算机系统详解》
    【第五周课堂作业】创新案例分析
    【第六周读书笔记】计算机体系结构读后感
    个人总结——马睿淳
    团队项目心得(6.15)
    团队项目心得
    《代码大全》读书笔记——第九周
    《我是一只IT小小鸟》(续)读书笔记——第八周
  • 原文地址:https://www.cnblogs.com/findumars/p/4740109.html
Copyright © 2020-2023  润新知