转自:http://www.cnblogs.com/felixYeou/archive/2008/11/23/1339205.html
第2部分和第3部分是系列文章的关键,着重理解。
在上一节里我们已经大致了解了活动对象的基本概念,要使用活动对象机制,需要用到活动对象、活动调度器、异步函数。我们想使用异步函数,要按照应用程序->活动对象->活动调度器->异步函数的流程来使用。接下来我们开始进入实战,使用活动对象。
一、创建活动调度器
我们知道,活动调度器是应用程序和异步函数之间的桥梁,应用程序使用活动对象通过活动调度器去截获异步函数的返回“完成”消息,并以事件的方式通知应用程序。
使用Carbide C++ 1.3,通过模板向导生成的控制台程序自动为我们生成了创建活动调度器的代码:
CActiveScheduler* scheduler = new (ELeave) CActiveScheduler(); CActiveScheduler::Install(scheduler);
CActiveScheduler::Install()方法调用以后,内部代码就会将scheduler指针赋值给CActiveScheduler类内部的静态指针,后面的代码就可以方便的使用CActiveScheduler类的静态方法,如:
IMPORT_C static void Add(CActive* aActive);
IMPORT_C static void Start();
IMPORT_C static void Stop();
- Add()方法:将活动对象加入活动调度器中注册,以备使用
- Start()方法:启动活动调度器,活动调度器将开始循环等待异步函数返回的通知消息
- Stop()方法:停止活动调度器
二、创建活动对象
1、我们创建的活动对象必须派生自CActive类,CActive类已经为我们准备好了iStatus成员变量:
public : TRequestStatus iStatus; private : TBool iActive;
另外一个成员变量iActive起着标识作用,证明该活动对象已经请求了异步函数,如:
RTimer::After(iStatus, 1000000 ); SetActive();
SetActive()方法为基类CActive的方法,其实就是将iActive = ETrue;,用来标识活动对象已经调用了异步函数。所以,我们只要调用了异步函数,在调用异步函数的代码后面应该紧挨着调用SetActive()方法的代码。
2、有两个虚方法必须继承:
virtual void DoCancel() = 0 ; virtual void RunL() = 0 ;
- RunL方法:活动调度器接收到异步函数返回的“完成”消息后,遍历在其注册的所有活动对象,如果活动对象的iActive = ETrue且iStatus != KRequestPending则调用活动对象的RunL方法,并将iActive设置成EFalse,以防下次轮询时仍然调用此活动对象。
在这里“RunL”这个名字会让很多人产生歧义,我刚开始接触的时候总以为和J2me中的Runnable接口的run方法差不多。其实在这里把“RunL”改为“NotifyRequestCompleteL”更贴切些。
再次声明一下,调用异步函数时,参数TRequestStatus& status都是以引用的方式传递的,如:
IMPORT_C void After(TRequestStatus & aStatus, TTimeIntervalMicroSeconds32 anInterval);
所以异步函数内部可以改变status的实参,也就是改变活动对象的类成员iStatus。 - DoCancel()方法:基类CActive中有取消异步函数的方法Cancel(),调用Cancel()后,活动对象会通过DoCancel()方法通知应用程序做取消方法的后期工作,如删除对象及回收指针等。注意:在应用程序中如果想终止活动对象,要使用Cancel()方法调用而不是DoCancel()方法。
3、活动对象的带优先级的构造函数:
基类CActive的构造函数原型如下:
protected : IMPORT_C CActive(TInt aPriority);
此处将传入一个优先级枚举值,枚举值内容如下:
/* * Defines standard priorities for active objects. */ enum TPriority { /* * A low priority, useful for active objects representing background processing. */ EPriorityIdle =- 100 , /* * A priority higher than EPriorityIdle but lower than EPriorityStandard. */ EPriorityLow =- 20 , /* * Most active objects will have this priority. */ EPriorityStandard = 0 , /* * A priority higher than EPriorityStandard; useful for active objects handling user input. */ EPriorityUserInput = 10 , /* * A priority higher than EPriorityUserInput. */ EPriorityHigh = 20 , };
当调用CActiveScheduler::Add方法注册活动对象时,活动调度器会按照活动对象的优先级进行排序,插入或添加到活动对象集合中。在此会起到如下作用:当多个异步函数消息同时返回时(多个iStatus同时不为KRequestPending),活动对象调度器轮训集合的时候总是会先找到优先级高的活动对象并调用其RunL方法。
但是在通常情况下,我们会在构造函数传入EPriorityStandard。
三、活动调度器Start方法的伪代码
通过以上两点分析,我们完全可以模拟出CActiveScheduler::Start方法:
void CActiveScheduler::Start() { for (;;) { // 挂起线程直到异步函数消息返回 // 注:活动调度器和应用程序不在一个线程,所以应用程序不会阻塞 User::WaitForAnyRequest(); // 如果异步函数和主程序在不同的线程则RThread::WaitForAnyRequest(); // 当消息返回的时候线程会苏醒 // 以优先级降序的方式检测调度器集合中每个活动对象 for (;;) { // 调用第一个已完成且iActive == ETrue的活动对象事件处理函数 if (activeObject->IsActive() && activeObject->iStatus != KRequestPending) { // 找到一个已准备好处理事件的活动对象 // 重置iActive状态以表明其不再是活动状态了 activeObject->iActive = EFalse; // 在TRAP中调用活动对象的事件处理函数 TRAPD(err, activeObject->RunL()) ; if (err != KErrNone) { // 如果异常则调用活动对象的RunError方法 err = activeObject->RunError(); if (err != KErrNone) { Error(err); } } break; } } } }
WaitForAnyRequest()比较关键,对它进行分析:这个成员函数应该是间接调用User::WaitForAnyRequest(),根据Vincent的说法:
/* User::WaitForAnyRequest到底做了什么呢?原来对于一个线程,在user mode下是RThread,同时在kernel里面有一个DThread和它对应。DThread有一个RequestSemaphore的成员。
异步请求别的RThread,也就是一个server来做某件事,当server做完以后,它就会来修改这个semaphore,把semaphore加一。
当RThread(application)调用 User::WaitForAnyRequest后就会把semaphore的值减一。当semaphore的值为-1时线程就suspend. */
//从Vincent的话中可以了解到:WaitForAnyRequest就是等待异步操作完成。显然,在某个时间,执行到 WaitForAnyRequest()时,semaphore很有可能是一个大于0的正整数N,这说明目前有N个异步操作完成了,这些异步操作可能是文件操作,也可能是网络、按键、Timer、UI等。
WaitForAnyRequest判断semaphore是否是-1,是的话说明没有任何事件需要处理,但是总不能退出程序吧,也不能跑一个死循环白白浪费CPU吧,所以显然应该把线程挂起,直到semaphore被Kernel改动后(即有异步操作完成了),线程才被唤醒。然后继续执行下面的代码。
// 注:活动调度器和应用程序不在一个线程,所以应用程序不会阻塞
经过Console程序的验证,Console应用的AS是在当前线程中的。CActiveScheduler::Start中通过WaitForAnyRequest就是会阻塞当前主进程,也因为这样的调用程序入口的Main函数不会马上执行完毕。
所以,CActiveScheduler::Start() 只能在至少有一个活动对象请求发出的时候之后才能被调用,因为Start先调用的话线程就被阻塞住了,后面的活动对象请求就无法执行到,那么活动调度器就是在“空等待”,线程一直在挂起、启动循环的状态。
原文意思应该是指S60 Application应用中AS在主线程以外的线程,Application中AS是默认创建好的,这部分代码是Nokia封装好的,不知道具体实现是什么样。