您的位置:首页 > 编程语言 > Delphi

认识Delphi的线程类

2008-03-31 17:11 429 查看
 本文是没有写过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类对线程的封装应该有所了解了,关于线程同步要用到信号灯,事件,临界区,互斥量等我会在下一篇里做个简要的介绍.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: