Linux中国 Linux中国门户站!
设为主页 设为主页
收藏本站 收藏本站
 
当前位置 :首页 ->编程语言 ->Delphi ->正文

剖析TThread类

来源:Linuxdby.com 作者:Webmaster 时间:2007-06-05 点击: [收藏] [投稿]


    为什么要建立一个线程窗体呢?答案就是TThread中的同步函数Synchronize()的需要。线程对象存取其他VCL的属性时与其他线程的同步机制是通过消息队列来实现的。当线程函数执行Synchronize()时,他就向线程窗体发送一条CM_EXECPROC消息。因为线程窗体是进程的一个窗体(虽然它不可见),所以发向线程窗体的消息都会进入进程消息队列,而消息队列的串行处理的特性保证不会出现访问冲突。这是一个简单而有效的解决方案。我不知道有没有人在控制台程序中应用多线程,如果有的话,TThread类可能就不太适合了。这种情况下要么直接应用线程函数,要么自己写一个新的TNewThread类了。


    Delphi是在TThread类的外面声明了一个局部函数ThreadProc。这个函数就是Windows SDK中介绍的线程函数,其声明如下:


function ThreadProc(Thread: TThread): Integer;
var
  FreeThread: Boolean;
begin
  try
    Thread.Execute;
  finally
    FreeThread := Thread.FFreeOnTerminate;
    Result := Thread.FReturnValue;
    Thread.FFinished := True;
    Thread.DoTerminate;
    if FreeThread then Thread.Free;
    EndThread(Result);
  end;
end;


    Delphi没有将线程函数作为TThread的一个成员函数,我想把ThreadProc放到TThread的Proctected段中TThread的灵活性可能会更好一点,不过现在的方法也不错。可以看到ThreadProc以TThread对象作为Parameter参数。这样可以保证TThread对象进入线程的堆栈中,一个TThread对象不破坏另一个同类型TThread对象的数据。当然,创建线程的线程还是可以访问新线程中的数据的,Terminate过程就是这样做的。所以TThread的数据还是可能被其他线程破坏的。所以外部线程要访问线程的数据要小心处理,Terminate()是一个比较典型的:外部线程只写,内部线程只读就能很好的工作,如果两个线程都又读又写就可能导致逻辑混乱。


    TThread类在构造线程实例是没有直接调用CreateThread() API函数,而是使用了一个BeginThread()函数。不知是什么原因,该函数并没有相应的Delphi Help文档,只是在“TThreadFunc type”的介绍中一笔带过。可能是Borland认为它的参数在以后还会修改吧。不过该函数和CreateThread() API的参数是一模一样的。这是一个让人兴奋的地方,因为BeginThread()加入了Windows API没有的异常处理功能。有意思的是,Delphi在BeginThread()由创建了一个新的线程函数,而把原来的线程函数和参数打包成TThreadRec作为新函数的Parameter。有关Delphi5中BeginThread的定义如下:


type
  PThreadRec = ^TThreadRec;
  TThreadRec = record
    Func: TThreadFunc;
    Parameter: Pointer;
  end;


function ThreadWrapper(Parameter: Pointer): Integer; stdcall;
asm
  CALL _FpuInit
  XOR ECX,ECX
  PUSH EBP
  PUSH offset _ExceptionHandler  //新增加的Delphi的异常机制
  MOV EDX,FS:[ECX]
  PUSH EDX
  MOV EAX,Parameter
  MOV FS:[ECX],ESP

  MOV ECX,[EAX].TThreadRec.Parameter
  MOV EDX,[EAX].TThreadRec.Func
  PUSH ECX
  PUSH EDX
  CALL _FreeMem
  POP EDX
  POP EAX
  CALL EDX  //调用原来的线程函数

  XOR EDX,EDX
  POP ECX
  MOV FS:[EDX],ECX
  POP ECX
  POP EBP
end;


function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord;
ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord;
var ThreadId: LongWord): Integer;
var
  P: PThreadRec;
begin
  New(P);
  P.Func := ThreadFunc;
  P.Parameter := Parameter;
  IsMultiThread := TRUE;
  Result := CreateThread(SecurityAttributes, StackSize, @ThreadWrapper, P,
    CreationFlags, ThreadID);
end;


    让人觉得美中不足的地方是TThread类在调用BeginThread时传递的SercurityAttributes和StackSize参数分别是nil和0,使BeginThread()在调用CreateThread()时使用了缺省的安全设置和默认堆栈大小。有关这两个参数代表什么意义请查阅Windows SDK文档。


3.结束语


    由于时间仓促,简单介绍我认为Delphi的帮助文档中没有说明的部分。不知你看后有什么疑惑或是觉得我什么讲的不对的地方,请来信告知: zg@hzhistar.com 。请多多指教!


4.致谢

    "其实,你这篇文章只适用于Delphi5,Delphi6已经改变了Synchronize的做法,改用事件(Event)和临界区(CriticalSection)的配合来进行同步多线程对VCL控件的访问。其它还有些少改动的地方,相信你看源码就会发现。
    另外,(或许你已经知道了)Delphi的文档也很清楚地说明了,调用BeginThread和EndThread来替代Win32API的CreateThread和ExitThread(其实《Windows 核心编程》也指出了应使用开发环境提供的_beginthreadex等函数,具体原因看书吧),至于Delphi,调用BeginThread的一个非常重要的作用就是将全局变量IsMultiThread设为True,因为Delphi的许多运行机制是当该变量为True时才是线程安全的,例如GetMem和FreeMem函数。"
——摘自 "hgd" <hgd01@263.net>
 

 如果您对本文有任何疑问或者建议,请到讨论区发表您的意见: >> 论坛入口 <<



上一篇:理解类引用这种类型   下一篇:不知您是笨蛋,还是我是笨蛋,关于Delphi的大Bug

文章评论】 【收藏本文】 【推荐好友】 【打印本文】 【我要投稿】 【论坛讨论
更多相关文章
Power by linux-cn.com 粤ICP备05006655号