认识Delphi的线程类

 本文是没有写过delphi的多线程,对delphi6的线程类TThread不熟悉的人而写的,主要从 TThread的源代码入手.(其他版本的delphi,请参照此文自行理解)

 Delphi为多线程的实现专门封装了一个TThread类来实现,我们从Create函数入手来认识一下这个类,这里一般都是windows下的开发,所以先去掉linux环境的代码:

constructor TThread.Create(CreateSuspended: Boolean);
begin
  inherited Create;
  AddThread;
  FSuspended := CreateSuspended;
  FCreateSuspended := CreateSuspended;
  FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
  if FHandle = 0 then
    raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
end;

TThread的构造函数只有一个CreateSuspended参数,该参数标示线程创建后是否挂起.

AddThread过程主要是创建一个同步时要用到的list及对线程数做一个计数.

这里主要是一个BeginThread函数的调用,BeginThread是对windows API函数CreateThread的一个封装,大家可以点进去看以下,基本没做什么操作,CreateThread的一个重要参数就是线程函数的入口地址(这里的
@ThreadProc),该函数的内容就是线程要做的真正的事(对于后面的Pointer(Self),和CREATE_SUSPENDED参数第一个是用于向线程传入参数时所要用到的指针,这里指向了self线程本身,因为在线程函数ThreadProc里并没有看到使用到它这里就不先多说了,在以后我的完成端口的例子里我会介绍如何使用它,CREATE_SUSPENDED是告诉线程创建后先挂起).我们可以点进去看下ThreadProc函数的定义

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;
    Result := Thread.FReturnValue;
    Thread.FFinished := True;
    Thread.DoTerminate;
    if FreeThread then Thread.Free;
    EndThread(Result);
  end;
end;

这里我们可以看到线程函数是先判断Terminated ,否的话执行Execute,最后根据FreeOnTerminate的属性决定是否释放线程.我们看Execute的定义,它是一个纯虚方法,所以用户在用继承TThread类来实现多线程时必须要覆盖Execute方法,添加代码告诉线程要做什么事.

有的人要问了,既然上面的BeginThread创建线程时是挂起的,那线程是什么时候开始工作的呢,这里就有看具体,create函数传入的参数了,如果是false表示线程创建后立即执行,具体是在

procedure TThread.AfterConstruction;
begin
  if not FCreateSuspended then
    Resume;
end;

方法里,这个方法是覆盖了TObject的虚方法,它在类创建后被delphi自动调用,我们看到它就2句代码,即如果传入的为false时即调用Resume方法,当Create指定线程创建后挂起时,用户必须自己调用此方法让线程开始执行.

TThread类提供了一个同步函数Synchronize,用于在多线程时做好同步操作,它的代码如下:

procedure TThread.Synchronize(Method: TThreadMethod);
var
  SyncProc: TSyncProc;
begin
  if GetCurrentThreadID = MainThreadID then
    Method
  else
  begin
    SyncProc.Signal := CreateEvent(nil, True, False, nil);
    try
      EnterCriticalSection(ThreadLock);
      try
        FSynchronizeException := nil;
        FMethod := Method;
        SyncProc.Thread := Self;
        SyncList.Add(@SyncProc);
        ProcPosted := True;
        if Assigned(WakeMainThread) then
          WakeMainThread(Self);
        LeaveCriticalSection(ThreadLock);
        try
          WaitForSingleObject(SyncProc.Signal, INFINITE);
        finally
          EnterCriticalSection(ThreadLock);
        end;
      finally
        LeaveCriticalSection(ThreadLock);
      end;
    finally
      CloseHandle(SyncProc.Signal);
    end;
    if Assigned(FSynchronizeException) then raise FSynchronizeException;
  end;
end;

它只有一个参数Method,我们可以看到它的定义 TThreadMethod = procedure of object;说明它是一个过程类型,需要传入一个过程的过程名.这个函数的代码看上去不难,但你如果稍微仔细点又会发现好象有点不对劲,因为除了当当前线程是主线程时会直接调用Method过程,下面并没有调用Method过程,那么它是在哪调用同步过程的呢?其实delphi的线程处理里是把同步过程的代码放到了主线程里调用,即Application所在的线程。我们从上面看到当当前线程不是主线程时,它首先创建了一个事件(SyncProc结构定义了一个事件和一个TThread对象),并将当前线程对象绑定到SyncProc结构,同时把这个结构的地址加到AddThread(Create方法里调用的第一个过程)里创建的SyncList里,后面它执行了一句这样的代码

if Assigned(WakeMainThread) then
          WakeMainThread(Self);

从定义可以看到WakeMainThread是一个事件变量,而且默认指向nil,我们在classes单元里可以搜到只有刚出现的几个地方有出现WakeMainThread,也就是说它没有在这个单元里的任何地方被赋值或调用,那么它是在哪被赋值的呢?(如果没有被赋值,那它永远会是nil,这些代码在这里就完全没有意义了,borland的那些工程师不至于犯这么低级的错误),其实通过全文搜索,你可以发现它在Forms单元里被赋值了

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

procedure TApplication.HookSynchronizeWakeup;
begin
  Classes.WakeMainThread := WakeMainThread;
end;
原来饶了一圈它只是要给Application发一个WM_NULL消息,而HookSynchronizeWakeup也是在Application创建时被调用了。我们再来看看Application处理WM_NULL的代码,在哪找?当然是WndProc里先找了,发现就在这里调用了,而且是调用了classes单元的CheckSynchronize方法。原来饶了一圈又回来了。

function CheckSynchronize: Boolean;
var
  SyncProc: PSyncProc;
begin
  if GetCurrentThreadID <> MainThreadID then
    raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]);
  if ProcPosted then
  begin
    EnterCriticalSection(ThreadLock);
    try
      Result := (SyncList <> nil) and (SyncList.Count > 0);
      if Result then
      begin
        while SyncList.Count > 0 do
        begin
          SyncProc := SyncList[0];
          SyncList.Delete(0);
          try
            SyncProc.Thread.FMethod;
          except
            SyncProc.Thread.FSynchronizeException := AcquireExceptionObject;
          end;
          SetEvent(SyncProc.signal);
        end;
        ProcPosted := False;
      end;
    finally
      LeaveCriticalSection(ThreadLock);
    end;
  end else Result := False;
end;

第一句不用说了,既然放在主线程里执行,那不是主线程就报错。接着判断的ProcPosted已经在前面设置为true了,接下来就是对SyncList里的所有add进来要同步执行的函数循环执行并删除,执行完一个给它一个信号(SyncProc.signal)。

另外还有一个WaitFor方法,此方法会在Destroy调用,以便在线程类释放前等待线程执行结束后再做释放操作,Destroy方法里面的代码比较简单,这里不再多说了.说说WaitFor

function TThread.WaitFor: LongWord;
var
  H: THandle;
  WaitResult: Cardinal;
  Msg: TMsg;
begin
  H := FHandle;
  if GetCurrentThreadID = MainThreadID then
  begin
    WaitResult := 0;
    repeat
      { This prevents a potential deadlock if the background thread
        does a SendMessage to the foreground thread }
      if WaitResult = WAIT_OBJECT_0 + 1 then
        PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);
      Sleep(0);
      CheckSynchronize;
      WaitResult := MsgWaitForMultipleObjects(1, H, False, 0, QS_SENDMESSAGE);
      Win32Check(WaitResult <> WAIT_FAILED);
    until WaitResult = WAIT_OBJECT_0;
  end else WaitForSingleObject(H, INFINITE);
  CheckThreadError(GetExitCodeThread(H, Result));
end;

这里着重看repeat的循环里的代码,首先看下注释,意思是:这里的代码是防止一个潜在的线程死琐的可能,如后台线程向前台线程(主线程)SendMessage 一个消息时.这里可能最不理解的会时MsgWaitForMultipleObjects这个东西了,这个函数在这里的作用是,当在0毫秒的时间里H线程SendMessage 一个消息或发出一个事件信号时会得到一个WAIT_OBJECT_0或WAIT_OBJECT_0+1的返回值(具体看这个api的帮助,百度百科里有),当返回WAIT_OBJECT_0时表示函数关注的所有对象均发出信号(这里只有子线程一个对象),返回为WAIT_OBJECT_0+1时表示H线程有了一个SendMessage ,这时候要用PeekMessage取过来以防长事件的循环里出现消息死琐.(以上是本人理解,有不对的地方请大家指正.)

到此我们对TThread类对线程的封装应该有所了解了,关于线程同步要用到信号灯,事件,临界区,互斥量等我会在下一篇里做个简要的介绍.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值