Delphi中的线程类--之(5,大结局)

    技术2022-05-11  95

    Delphi中的线程类

     

    猛禽[Mental Studio]

    http://mental.mentsu.com

     

    之五(大结局)

    回到前面CheckSynchronize,见下面的代码:

    function CheckSynchronize(Timeout: Integer = 0): Boolean;

    var

      SyncProc: PSyncProc;

      LocalSyncList: TList;

    begin

      if GetCurrentThreadID <> MainThreadID then

        raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]);

      if Timeout > 0 then

        WaitForSyncEvent(Timeout)

      else

        ResetSyncEvent;

      LocalSyncList := nil;

      EnterCriticalSection(ThreadLock);

      try

        Integer(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList));

        try

          Result := (LocalSyncList <> nil) and (LocalSyncList.Count > 0);

          if Result then

          begin

            while LocalSyncList.Count > 0 do

            begin

              SyncProc := LocalSyncList[0];

              LocalSyncList.Delete(0);

              LeaveCriticalSection(ThreadLock);

              try

                try

                  SyncProc.SyncRec.FMethod;

                except

                  SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;

                end;

              finally

                EnterCriticalSection(ThreadLock);

              end;

              SetEvent(SyncProc.signal);

            end;

          end;

        finally

          LocalSyncList.Free;

        end;

      finally

        LeaveCriticalSection(ThreadLock);

      end;

    end;

    首先,这个方法必须在主线程中被调用(如前面通过消息传递到主线程),否则就抛出异常。

    接下来调用ResetSyncEvent(它与前面SetSyncEvent对应的,之所以不考虑WaitForSyncEvent的情况,是因为只有在Linux版下才会调用带参数的CheckSynchronizeWindows版下都是调用默认参数0CheckSynchronize)。

    现在可以看出SyncList的用途了:它是用于记录所有未被执行的同步方法的。因为主线程只有一个,而子线程可能有很多个,当多个子线程同时调用同步方法时,主线程可能一时无法处理,所以需要一个列表来记录它们。

    在这里用一个局部变量LocalSyncList来交换SyncList,这里用的也是一个原语:InterlockedExchange。同样,这里也是用临界区将对SyncList的访问保护起来。

    只要LocalSyncList不为空,则通过一个循环来依次处理累积的所有同步方法调用。最后把处理完的LocalSyncList释放掉,退出临界区。

    再来看对同步方法的处理:首先是从列表中移出(取出并从列表中删除)第一个同步方法调用数据。然后退出临界区(原因当然也是为了防止死锁)。

    接着就是真正的调用同步方法了。

    如果同步方法中出现异常,将被捕获后存入同步方法数据记录中。

    重新进入临界区后,调用SetEvent通知调用线程,同步方法执行完成了(详见前面Synchronize中的WaitForSingleObject调用)。

    至此,整个Synchronize的实现介绍完成。

     

    最后来说一下WaitFor,它的功能就是等待线程执行结束。其代码如下:

    function TThread.WaitFor: LongWord;

    var

      H: array[0..1] of THandle;

      WaitResult: Cardinal;

      Msg: TMsg;

    begin

      H[0] := FHandle;

      if GetCurrentThreadID = MainThreadID then

      begin

        WaitResult := 0;

        H[1] := SyncEvent;

        repeat

          { This prevents a potential deadlock if the background thread

            does a SendMessage to the foreground thread }

          if WaitResult = WAIT_OBJECT_0 + 2 then

            PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);

          WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);

          CheckThreadError(WaitResult <> WAIT_FAILED);

          if WaitResult = WAIT_OBJECT_0 + 1 then

            CheckSynchronize;

        until WaitResult = WAIT_OBJECT_0;

      end else WaitForSingleObject(H[0], INFINITE);

      CheckThreadError(GetExitCodeThread(H[0], Result));

    end;

    如果不是在主线程中执行WaitFor的话,很简单,只要调用WaitForSingleObject等待此线程的HandleSignaled状态即可。

    如果是在主线程中执行WaitFor则比较麻烦。首先要在Handle数组中增加一个SyncEvent,然后循环等待,直到线程结束(即MsgWaitForMultipleObjects返回WAIT_OBJECT_0,详见MSDN中关于此API的说明)。

    在循环等待中作如下处理:如果有消息发生,则通过PeekMessage取出此消息(但并不把它从消息循环中移除),然后调用MsgWaitForMultipleObjects来等待线程HandleSyncEvent出现Signaled状态,同时监听消息(QS_SENDMESSAGE参数,详见MSDN中关于此API的说明)。可以把此API当作一个可以同时等待多个HandleWaitForSingleObject。如果是SyncEventSetEvent(返回WAIT_OBJECT_0 + 1),则调用CheckSynchronize处理同步方法。

    为什么在主线程中调用WaitFor必须用MsgWaitForMultipleObjects,而不能用WaitForSingleObject等待线程结束呢?因为防止死锁。由于在线程函数Execute中可能调用Synchronize处理同步方法,而同步方法是在主线程中执行的,如果用WaitForSingleObject等待的话,则主线程在这里被挂起,同步方法无法执行,导致线程也被挂起,于是发生死锁。

    而改用WaitForMultipleObjects则没有这个问题。首先,它的第三个参数为False,表示只要线程HandleSyncEvent中只要有一个Signaled即可使主线程被唤醒,至于加上QS_SENDMESSAGE是因为Synchronize是通过消息传到主线程来的,所以还要防止消息被阻塞。这样,当线程中调用Synchronize时,主线程就会被唤醒并处理同步调用,在调用完成后继续进入挂起等待状态,直到线程结束。

     

    至此,对线程类TThread的分析可以告一个段落了,对前面的分析作一个总结:

    1、  线程类的线程必须按正常的方式结束,即Execute执行结束,所以在其中的代码中必须在适当的地方加入足够多的对Terminated标志的判断,并及时退出。如果必须要“立即”退出,则不能使用线程类,而要改用APIRTL函数。

    2、  对可视VCL的访问要放在Synchronize中,通过消息传递到主线程中,由主线程处理。

    3、  线程共享数据的访问应该用临界区进行保护(当然用Synchronize也行)。

    4、  线程通信可以采用Event进行(当然也可以用Suspend/Resume)。

    5、  当在多线程应用中使用多种线程同步方式时,一定要小心防止出现死锁。

    6、  等待线程结束要用WaitFor方法。

     

    Dec.01-03

    (终于续完了)

     


    最新回复(0)