Delphi中的线程类
猛禽[Mental Studio]
http://mental.mentu.com
之五(大结局)
回到前面CheckSynchronize,见下面的代码:
函数 CheckSynchronize(超时: 整数 = 0): 布尔值;
变量
SyncPROc:PSyncProc;
本地同步列表:TList;
开始
如果 GetCurrentThreadID <> MainThreadID 那么
引发 EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]);
如果超时 > 0 那么
等待同步事件(超时)
别的
重置同步事件;
LocalSyncList := nil;
EnterCriticalSection(ThreadLock);
尝试
整数(LocalSyncList):= InterlockedExchange(整数(SyncList),整数(LocalSyncList));
尝试
结果 := (LocalSyncList <> nil) 和 (LocalSyncList.Count > 0);
如果结果那么
开始
当 LocalSyncList.Count > 0 时
开始
SyncProc := LocalSyncList[0];
LocalSyncList.Delete(0);
LeaveCriticalSection(ThreadLock);
尝试
尝试
SyncProc.SyncRec.FMethod;
除了
SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;
结尾;
最后
EnterCriticalSection(ThreadLock);
结尾;
SetEvent(SyncProc.signal);
结尾;
结尾;
最后
LocalSyncList.Free;
结尾;
最后
LeaveCriticalSection(ThreadLock);
结尾;
结尾;
首先,这个方法在主线程中被调用(如前面通过消息必须传递到主线程),否则就发送异常。
调用接下来的ResetSyncEvent(它与前面SetSyncEvent对应的,不必考虑WaitForSyncEvent的情况,是因为只有在linux版本下才会调用带参数的CheckSynchronize,Windows版本下都是调用默认参数0的CheckSynchronize)。
现在可以看出SyncList的用途了:它是用于记录所有同步执行的同步方法的。因为主线程只有一个,而子线程可能有很多个,当多个子线程同时调用同步方法时,主线程可能一时无法处理,所以需要一个列表来记录它们。
这里用一个局部变量LocalSyncList来交换SyncList,这里用的也是一个原语:InterlockedExchange。同样,这里也用临界区如果SyncList的访问保护起来。
只要LocalSyncList不为空,则通过一个循环来依次处理累积的所有同步方法调用。最后把处理完的LocalSyncList释放掉,退出临界区。
再看看对同步方法的处理:首先是从列表中移出(取出并从列表中删除)第一个同步方法调用数据。然后退出临界区(原因当然也是为了防止死锁)。
接下来才是真正的调用同步方法了。
如果同步方法中出现异常,将被捕获后存入同步方法数据记录中。
重新进入临界区后,调用SetEvent通知调用线程,同步方法执行完成了(参见前面Synchronize中的WaitForSingleObject调用)。
至此,整个同步的实现介绍完成。
最后说一下WaitFor,它的功能就是等待线程执行结束。其代码如下:
函数 TThread.WaitFor: LongWord;
变量
H: THandle 的数组[0..1];
等待结果:Cardinal;
消息:TM消息;
开始
H[0] := FHandle;
如果 GetCurrentThreadID = MainThreadID 那么
开始
等待结果:= 0;
H[1] := 同步事件;
重复
{ 如果后台线程
向前台线程发送 SendMessage }
如果 WaitResult = WAIT_OBJECT_0 + 2 那么
PeekMessage(消息, 0, 0, 0, PM_NOREMOVE);
WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);
CheckThreadError(WaitResult <> WAIT_FAILED);
如果 WaitResult = WAIT_OBJECT_0 + 1 那么
检查同步;
直到等待结果= WAIT_OBJECT_0;
结束 else WaitForSingleObject(H[0], INFINITE);
CheckThreadError(GetExitCodeThread(H[0], 结果));
结尾;
如果不是在主线程中执行WaitFor的话,很简单,只需调用WaitForSingleObject等待此线程的Handle为Signaled状态即可。
如果是在主线程中执行WaitFor则比较麻烦。首先要在Handle队列中增加一个SyncEvent,然后循环等待,直到线程结束(即MsgWaitForMultipleObjects返回WAIT_OBJECT_0,参见MSDN中关于此API的说明)。
在循环等待中作如下处理:如果有消息发生,则通过PeekMessage取出此消息(但并不把它从消息循环中移除),然后调用MsgWaitForMultipleObjects来等待threadHandle或SyncEvent出现Signaled状态,同时监视监听消息(QS_SENDMESSAGE参数,请参阅MSDN中关于此API的说明)。可以把此API设为一个可以同时等待多个Handle的WaitForSingleObject。如果是SyncEvent被SetEvent(返回WAIT_OBJECT_0) + 1),则调用CheckSynchronize处理同步方法。
在主线程中调用WaitFor必须用MsgWaitForMultipleObjects,而不能用WaitForSingleObject等待线程结束呢?因为因为防止死锁。由于在线程函数Execute中可能调用Synchronize处理同步方法,而同步方法是在主线程中执行的,如果使用WaitForSingleObject等待的话,则主线程在这里被挂起,同步方法无法执行,导致线程也被挂起,从而发生死锁。
而改用WaitForMultipleObjects则没有这个问题。首先,它的个参数为False,表示只要threadHandle或SyncEvent中只要有一个Signaled第三即可使主线程被唤醒,直至加上QS_SEND MESSAGE是因为Synchronize是通过消息传到主线程来的,所以还要防止消息被阻塞。这样,当线程中调用Synchronize时,主线程就会被唤醒并处理同步调用,在调用完成后继续进入挂起等待状态,直到线程结束。
至此,对于线程类TThread的分析可以告一个段落了,对前面的分析作一个总结:
1、 线程类的线程必须以正常的方式结束,即执行执行结束,所以在其中的代码中必须在适当的位置加入足够多的对终止标志的判断,并及时退出。如果必须“立即”退出,则不能使用线程类,而要改用API或RTL函数。
2、 对可视VCL的访问要放在Synchronize中,通过消息传递到主线程中,由主线程处理。
3、 线程共享数据的访问应该用临界区进行保护(当然用Synchronize也行)。
4、 线程通信可以采用Event进行(当然也可以用Suspend/Resume)。
5、当在多线程应用中使用多种线程同步方式时,一定要小心防止出现死锁。
6、等待线程结束使用WaitFor方法。
12月1-3日
(终于完结了)