【IT168 技术】 活动对象和活动调度器
1. 动机
事件驱动环境中的一些任务(如各种 UI 应用中的一些任务)处理来自系统的一些事件。程序
代码往往调用某个异步方法,它处理一个请求。当该请求完成之后,客户端得到提示,处理 结果,且很有可能发起一个新的请求。
如果用一些线程来实现某些任务,针对每一个事件源都必须有一个线程。例如,某个线程可
处理键按下。当按下某个键后,它可能会去请求服务器发出一个提示。然后该线程就会被挂
起,直到服务器唤醒该客户端线程去处理那次按键。同时,可能还存在着另一个线程,它以 某个异步(或阻断同步)方法以某种类似方式从 socket 读取数据。
各种活动对象提供了事件处理的便捷方式。当发出一个异步请求并当服务器完成该请求时,
活动对象的内部属性得到了传递,以取得响应代码。活动调度器关注该活动对象的运行,及 当该请求在服务器端得到完成后对结果的处理。
有很多理由解释在 Symbian OS 中为什么用活动对象,而不是线程,来处理各种事件。例 如:
ƒ 线程间的通信比活动对象间的通信缓慢且困难些(活动对象在同一个线程中运 行,因此它们不需要任何特殊方法来共享数据)。
ƒ 线程间的场景切换消耗 CPU 周期,从而使其性能表现下降。活动对象并不像线 程那样会被中断,因此并不需要在各个切换间储存并恢复 CPU
及内存映射单元
(Memory Mapping Unit,MMU)的状态。
ƒ 许多资源不能在线程间共享。由于活动对象用一个线程运行,它们可以共享资 源。
ƒ 在线程间访问内存或访问其他共享内存通常需要对同步更新提供保护。这由信号
量(semaphores)完成,这可能导致代码的复杂化,并造成性能下降。活动对 象不用担心同步问题,因为它们是以某种非抢先方式调度的。
2. 活动对象
Symbian OS 基于微内核结构。各种服务的使用都通过异步接口。活动对象和活动调度器针
对异步服务的使用提供了方便的框架:某个活动对象发起一些异步请求,并处理该请求。有
一个变量用于指出服务器何时已完成该请求。活动调度器追踪那些活动对象,当其发现有一 个请求已完成时,它会调用所分配的活动对象的
RunL()方法,而后者会去处理该服务器响 应。
继承自 CActive 的某个类被称为一个活动对象。CActive 被如下声明:
1: class CActive : public CBase
2: {
3: public:
4: enum TPriority
5: {
6: EPriorityIdle=-100,
7: EPriorityLow=-20,
8: EPriorityStandard=0,
9: EPriorityUserInput=10,
10: EPriorityHigh=20,
11: };
12: public:
13: IMPORT_C ~CActive();
14: IMPORT_C void Cancel();
15: IMPORT_C void Deque();
16: IMPORT_C void SetPriority(TInt aPriority);
17: inline TBool IsActive() const;
18: inline TBool IsAdded() const;
19: inline TInt Priority() const;
20: protected:
21: IMPORT_C CActive(TInt aPriority);
22: IMPORT_C void SetActive();
23: // Pure virtual
24: virtual void DoCancel() =0;
25: virtual void RunL() =0;
26: IMPORT_C virtual TInt RunError(TInt aError);
27: public:
28: TRequestStatus iStatus;
29: private:
30: TBool iActive;
31: TPriQueLink iLink;
32: friend class CActiveScheduler;
33: friend class CServer;
34: friend class CServer2;
35: };
通常, 活动对象用于发出一个异步调用,并当服务供应商处理完请求后去处理这些结果。流 程如下:
1. 创建活动对象并将其添加到活动调度器中
2. 要求活动对象创建一个异步请求。该活动对象将其 iStatus 作为针对该异步方 法的一个参数传递(请见 2.3 节)。这个异步方法将
iStatus 变量设定为 KRequestPending,以表示该活动对象正在等待某个请求的完成,并将请求消
息发送给服务供应商。然后,异步方式返回。在活动对象的方法返回之前,它调 用 SetActive()方法,后者向活动调度器指出:必须追踪这个活动对象的完
成。
3. 当服务供应商完成请求之后,它将结果代码赋予 iStatus 变量。
4. 活动调度器追踪被激活的活动对象。如果活动对象的 iStatus 不是
KRequestPending,就调用其 RunL()方法,因为服务器端的请求已经就绪。
5. Method RunL() processes the result code of the previous asynchronous
request.
方法 RunL()处理先前的异步请求的结果代码。 上面讲述的范例十分简单:活动对象被用于仅完成一个异步请求。然而,RunL()本可以发
出一个新的异步请求,激活自己,并等待,直到活动调度器再一次调用其 RunL()。 为了正确使用活动对象,理解某些特性和行为是很有帮助的。(请见
3. 活动调度器 内部是如何工作的)
ƒ 状态。 当某个活动对象调用了一个异步方法,它将其 iStatus 作为参数传递。 服务供应商立即将状态设定为
KRequestPending,这表示:服务供应商正在处 理该请求。
ƒ 活动性。当某个活动对象发出了一个异步请求,就必须以 SetActive()方法对
其进行激活。这是因为:活动调度器只对活动着的活动对象进行追踪。当活动对 象被激活后,它将保持其活动状态直到活动调度器调用其 RunL()方法或当前的
任务已被删除时为止。
ƒ 优先权。活动调度器按优先权顺序追踪各个活动对象。针对活动对象的优先权在 构造时给出。当活动对象没被激活时,可以用
SetPriority()方法修改其优先 权(这样某个请求就不再处于等待状态)。
ƒ 结果处理。当某个活动对象是活动的,且其 iStatus 不是 KRequestPending,这意味着:服务供应商已经完成了该活动对象所发出的请
求。当活动调度器发现这样一个对象时,它使这一对象不再活动并调用其 RunL() 方法。
方法 RunL()是一个“纯虚”方法,因而必须在继承类中实现。异步请求的结果 代码可以从 iStatus
参数中找到。该方法的实现几乎可以做任何事情,但不能 持续太长时间!
注意:RunL()方法一定不能持续太久,因为活动对象是以非抢先方 式调度的。同一线程中所有其他活动对象在 RunL()执行期间被阻
断。许多活动对象(如键盘事件处理器和屏幕刷新器等)需要快速响 应这些事件,因此不能阻断太长时间。
ƒ 取消。活动对象正在执行的任务必须能被取消。当调用活动对象的 Cancel()方 法时,活动调度器调用该活动对象的
DoCancel()方法,然后使这一对象不再活 动。试图取消某个非活动的活动对象(因此无需等待任何请求的完成)不会产生 任何效用
DoCancel()是一个“纯虚”方法,因此必须在某个具体继承类中实现。该实现 必须取消当前正在等待中的异步请求。
具有异步方法的那些类往往具有用于取消那些等待中请求的方法。当调用这种取 消方法时,服务供应商将取消当前请求并往往相应地把 iStatus 设定为
KErrCancel。在客户端线程从这个被取消的方法中返回之前,它等待来自服务 供应商有关已经取消该操作的提示。
ƒ 出错处理。活动调度器捕捉 RunL()方法的执行。如果它出现了异常,活动调度 器会调用该活动对象的
RunError()方法并将某个异常代码作为参数传递出去。 如果 RunError()返回一个非零返回码,活动调度器将发出当前线程的资源紧缺 警报。
RunError()方法是虚的,且可以被重置。默认实现返回作为一个参数传递的出 错代码(因此活动调度器发出当前线程的资源紧缺警报)。