Presentation is loading. Please wait.

Presentation is loading. Please wait.

 活动对象(Active Object).

Similar presentations


Presentation on theme: " 活动对象(Active Object)."— Presentation transcript:

1  活动对象(Active Object)

2 活动对象 活动对象被用于事件驱动的多任务处理 本讲要讲解为什么活动对象非常重要 它们是 Symbian OS的一个基本部分
解释它们是如何怎样设计以进行响应式、高效率的处理事件

3 活动对象 Symbian OS中的事件驱动多任务处理 理解同步请求和异步请求的区别之处,并且能够对它们的典型进行区分
认识活动对象的典型应用——处理异步任务并且不阻塞线程时被请求 理解用多线程和多活动对象实现多任务的区别,以及为什么后者在Symbian OS 编码中要优先使用

4 Symbian OS 中的事件驱动多任务处理
同步和异步请求 当程序的函数代码中要调用一个服务请求时,这个服务不是同步执行就是异步执行 同步函数 执行一个服务直到完成,然后返回其调用者,通常返回的是一个成功或者失败指示 异步函数 提交一个请求作为函数调用的一部分,并且马上返回到其调用者继续执行 在一段时间之后请求再完成

5 Symbian OS 中的多任务处理 调用一个异步请求以后 这个信号就是一个事件 定时器等待是一个典型的异步调用的例子
调用者可以自由处理其他问题或者简单的等待,后者就是常说的“阻塞” 一旦服务完成,调用者可以接收到一个信号,显示请求是否成功 这个信号就是一个事件 这段代码被称为事件驱动 定时器等待是一个典型的异步调用的例子 另一个例子是Read()方法,它在Symbian OS的RSocket类中,这个方法是从远程主机收 取数据 Sockets 在以后的讲座中讲解

6 Symbian OS 中的线程 线程被内核以抢占的方式调度 内核控制线程的调度
内核执行已经就绪的最高优先级的线程 每个线程都可能由于等待一个事件的发生而暂停执行,而在适当的条件下恢复执行 内核控制线程的调度 允许线程通过时间片分割的方式共享系统资源,如果有更高级的线程处于就绪态,则抢先执行这个 高优先级的线程 这种总是运行已就绪的最高优先级线程的方式是占先式多任务处理的基础 The student is expected to understand basic thread and process models already. Students who wish to read more about this may like to consult “Smartphone Operating System Concepts with Symbian OS” by Michael J Jipping, published by Symbian Press, 2007.

7 Symbian OS中的线程 当当前线程挂起时会发生上下文切换
上下文切换导致了运行时内核调度器的负载 如果原来的和替换的线程在不同的进程执行,会因为要交换进程内存以及写入缓存而带来 更大开销 比线程上下文切换要慢100倍 The student is expected to understand basic thread and process models already.

8 事件驱动多任务处理 异步事件可发生在如下情况: 从外部资源——例如用户输入或者硬件外设接收到数据。
通过软件——例如由定时器或者完成异步请求时引起。

9 事件驱动多任务处理 事件由事件处理器管理 事件处理器等待事件,然后处理它 一个高层的事件处理器的应用是网页浏览器应用
等待用户输入和响应,提交请求以接收Web页,并把接收到的页面显示出来 网页浏览器使用系统服务器,用以接收来自他客户端的请求。系统服务器接收到请求,并且继续等待另一个请求。 在服务请求中,系统服务器依次将请求地交给其他的服务器,它们将稍后产生完成事件。 这里描述的每个软件组建都是事件驱动的。它需要对用户输入或者来自系统的请求作出响应 这样的话很快就会变得很复杂!

10 Symbian OS 中事件处理考虑 在响应一个事件的时候,事件处理器可能要请求另一个服务,而这将引发另 一个事件(依次类推)
操作系统必须有一个高效的事件处理模型,从而在事件发生后通过最合适的顺序尽快处 理他们 对用户驱动的事件进行快速处理及给出响应以提供良好的用户体验是尤为重要的

11 Symbian OS 中事件处理考虑 要避免在事件之间持续的检测 操作系统在低能耗状态进行等待 事件处理应使内存资源使用最小化
这样不断的检测会导致大量的功耗,这在电池供能的设备上必须避免 操作系统在低能耗状态进行等待 在等待下一个事件的时候,软件应允许操作系统转入空闲模式 事件处理应使内存资源使用最小化 处理器资源也要有效的使用 活动对象满足这些要求,并提供了一种轻量级的事件驱动多任务处理机制

12 活动对象和活动调度器 活动对象和活动调度器(Active Scheduler) 每个活动对象封装了一个任务 一起被称为“活动对象框架”
用于简化异步程序设计,使编写代码更容易: 提交异步请求 管理请求的完成事件 对结果的处理 通常,一个Symbian OS应用程序或服务包含单个主事件处理线程和与其相关的活动调度器 在线程中运行一组活动对象 活动对象有的事件处理方法以供活动调度器调用 每个活动对象封装了一个任务 它向其服务器提供者请求异步服务,并由活动调度器调用它处理请求完成事件

13 活动对象和活动调度器 活动对象框架被用于调度 所有活动对象都在同一个线程中运行,因此在活动对象中切换比线程间上下文切 换的代价要低
同一个线程中多个异步任务的处理 所有活动对象都在同一个线程中运行,因此在活动对象中切换比线程间上下文切 换的代价要低 这就使得活动对象通常是Symbian OS 中最合适的事件驱动多任务处理机制 活动对象在运行中是彼此独立的 就像在进程中线程的运行时彼此独立的一样 然而,由于在在同一线程中,活动对象更容易共享内存

14 活动对象和活动调度器 活动对象框架 当活动对象在处理事件时 是协作式或者非抢先的多任务处理
在线程中其他活动对象能够开始执行操作之前,每个活动对象的方法都会运行直到完成 当活动对象在处理事件时 它不能被线程中其他运行的活动对象所抢占 注意线程本身的调度是抢先式的(见前面的幻灯片)

15 活动对象和活动调度器 一个Win32 应用(例如在Windows下运行的程序) 使用了一个简单的消息循环和消 息分发的模式
在 Symbian OS 中,活动调度器代替了Windows中的消息循环,活动对象的事件处理函数相 当于消息处理函数 由活动调度器执行的事件完成处理与事件引发的特定动作是解耦的——它们由单个活动对象执 行 例如: 发送完成事件——其处理动作就移开了”发送”对话框

16 实时性考虑说明 有些事件要求在有保证的时间内响应 不同任务对实时响应有不同的需求 这被称为“实时”事件处理
例如一个实时任务可能被要求保持为一个声音驱动的缓冲区提供声音数据,相应延迟就会导致声音 解码的延迟,其结果就是声音的中断 另一个典型的实时要求可能更严格,例如底层的电话通信 不同任务对实时响应有不同的需求 这些可以用任务优先级表示 高优先级任务总必须能抢占低优先级任务,从而保证符合他们的实时性要求 任务要求的响应越短,它应赋予的优先级越高

17 活动对象不适合实时任务 当一个活动对象在处理事件时,它无法被同一线程的另一个活动对象的事 件处理程序所抢占
因此,他们不适合实时任务 在 Symbian OS中, 实时任务应当用高优先级线程和进程来实现,并 根据相对的实时性要求选择适当的优先级

18 活动对象 CActive类 了解活动对象优先级别的重要性 知道活动对象事件处理方法 (RunL()) 是非抢占的
知道活动对象的继承特性,以及需要实现和覆盖的函数 了解如何构建、使用和销毁活动对象

19 活动对象 引言 每个活动对象请求一个异步服务,并且在请求过后一段时间处理完成事件 它也提供一种撤销未完成请求的方法,还有异常情况下的错误处理
 活动对象 引言 每个活动对象请求一个异步服务,并且在请求过后一段时间处理完成事件 它也提供一种撤销未完成请求的方法,还有异常情况下的错误处理 一个活动对象类必须派生自CActive 类,该类定义在e32base.h中

20 活动对象类的构造 CActive 类是一个抽象类,拥有两个纯虚函数 在构造时
RunL() 和 DoCancel() – 所有具体的活动对象类必须从CActive类中继承,定义和实 现这些方法 它还拥有一个TRequestStatus 成员变量用以接收异步请求的完成结果 在构造时 从CActive派生的类必须调用基类中的保护型构造函数 同时传递一个参数来设置活动对象的优先级 和线程类似,所有的活动对象都有一个优先级值来决定它们被如何调度

21 为什么有活动对象优先级? 当活动对象关联的异步服务完成时会产生一个事件,活动调度器能侦测到这 个事件. 当活动对象在处理事件时
活动调度器决定每个事件关联的是哪个活动对象 然后调用恰当的活动对象去处理事件 当活动对象在处理事件时 直到事件处理函数返回到活动调度器,它是无法被抢占的 因此很可能很多事件在控制返回到活动调度器之前已经完成……

22 为什么有活动对象优先级? 要解决哪个活动对象是下一个要运行的 优先级只是用来决定事件处理者运行的顺序的
活动调度器通过活动对象的优先级进行排序,而不是根据完成的顺序 否则,一个具有低优先级的事件,如果他在一个更重要的事件之前完成,那么它就会将高优先级 事件的处理推迟一段时间,而这段时间的长度是不确定的 优先级只是用来决定事件处理者运行的顺序的 如果一个高优先级活动对象收到一个事件处理请求,而此时一个低优先级的活动对象正处理事件, 这个低优先级的事件处理器是不能被抢占的

23 优先级 定义了一系列的优先级 在CActive 类中的TPriority 枚举类
通常使用优先级 CActive::EPriorityStandard (=0),除非有很好的理由不去这么做

24 活动对象类的构造 作为构造的一额外部分,活动对象代码应当调用活动调度器的静态函数 CActiveScheduler::Add()
这可以将活动对象加入到那个线程中的事件处理活动对象列表中 列表由活动调度器来维护 列表用活动对象的优先级排序,最高优先级的活动对象在前面

25 活动对象类的构造 活动对象典型的拥有的一个对象的句柄 向它发出异步完成的请求,例如RTimer 类型的定时器对象
这个对象通常被认为是异步服务提供者,它可能需要作为活动对象构造的一部分被初始化 如果初始化失败,它必须在第二阶段构造中执行

26 提交异步服务请求 活动对象类 如下所示... 会向调用者提供公有的请求发起方法以初始化请求
这些方法通过建立好的模式向活动对象对应的异步服务者提交请求 过段时间请求完成,产生一个事件 如下所示...

27 提交异步服务请求 1. 检查以前的未完成请求 请求方法应该检查在试图提交另一个请求之前没有请求已被提交了.
1. 检查以前的未完成请求 请求方法应该检查在试图提交另一个请求之前没有请求已被提交了. 每一个活动对象只能有一个已未完成的请求. 依赖于实现,代码可能有如下几种情况: 如果请求已经发出,就发生致命系统 (这种情况只会因为程序设计错误而发生) 如果尝试提交多个请求是合法饿,拒绝提交另一个请求, 取消先前的请求并提交一个新的请求

28 提交异步服务请求 2. 请求的结果 活动对象应该向服务提供者发送请求,传入其 TRequestStatus& 类型的iStatus成员变量 作为参数 服务提供者在开始异步请求之前要把这个值赋给 KRequestPending

29 提交异步服务请求 3. 调用 SetActive() 方法标识对象为“等待”状态
如果请求被成功的提交了,请求方法将调用CActive 基类中的SetActive()方法 从而向活动调度器指明请求是已经被提交并且是未完成的 只有在请求已经提交了以后,才能调用这个函数

30 事件处理 每个活动对象类 RunL() 方法 必须实现 CActive 基类的纯虚成员函数 RunL()
这是当来自异步服务提供者的完成事件发生时调用的事件处理函数 活动调度器选择了一个活动对象处理该事件时,就调用这个函数 RunL() 方法 这函数的名字有一定的误导性,因为异步函数已经在运行了 或许 HandleEventL() 或者 HandleCompletionL()这样的名字会更清楚的描述其功 能

31 事件处理 RunL()的典型行为 一旦RunL()开始执行
通过监测活动对象的 TRequestStatus 对象中的iStatus 即一个32位整形值,来判定异步 请求是否成功完成 RunL() 通常或者发送另一个请求或者告知系统中其他对象事件完成 RunL() 的代码复杂程度可能变化很大 一旦RunL()开始执行 它无法被其他活动对象的事件处理器所抢占 正是由于这个原因代码应该尽快完成,这样其他事件才能够无延误的处理

32 事件处理 活动对象 异步服务提供者 1. 提交请求并传递 iStatus 3. 对自身调用SetActive()
此图展示了当前活动对象向一个异步服务提供者提交一个请求,一段时间后请求完成了,产生一个事件,而事件被 RunL()处理时基本的动作执行序列 活动对象 异步服务提供者 1. 提交请求并传递 iStatus 3. 对自身调用SetActive() 2. 设置 iStatus=KRequestPending 并开始服务 4. 服务完成,服务提供者通过 RequestComplete() 来通知活动调度器,并传递一个完成结果 5. 活动调度器对活动对象调用 RunL() 来处理该事件 6. RunL() 重新提交另一个请求,并且不可被抢占 活动规划器 进程或线程边界 进行服务处理

33 撤销未完成的异步请求 一个活动对象 CActive 基类 必须能够撤销任何未完成的、自己所产生的非同步请求
例如,活动对象所在的应用程序线程将要终止,他必须能够阻止请求 CActive 基类 实现了一个 Cancel() 方法,该方法调用纯虚方法 DoCancel()然后等待请求尽早完成 所有的 DoCancel() 实现都应该对异步服务提供者调用恰当的撤销函数 This is covered more, later in the active objects module

34 撤销选定的异步请求 DoCancel() 也可以包括其他处理 不需要检查 ... 但它不应该异常退出或分配资源,而且也不应该执行长时间的操作
限制该方法只执行撤销工作以及与与撤销关联的需要清除工作,而非实现复杂的功能, 是一个很好的准则 这是因为析构函数应该调用Cancel(),而可能已经清除了DoCancel() 函数所需要的 资源 不需要检查 ... 在调用 Cancel()之前,请求是否已经被被选中的 这样做也是安全的,即便它当前不是活动的,例如正在等待事件 This is covered more, later in the active objects module

35 错误处理 从Symbian OS v6.0 之后 如果活动对象能够处理 RunL()中发生的所有异常退出
活动对象提供了 RunError() 虚方法,当活动对象的RunL()方法中发生异常退出的 时候,活动调度器会调用这个方法 该方法接受一个异常退出码作为参数,返回一个错误码用以表明异常退出是否已经被处 理了 缺省的实现是不处理异常退出,而只是简单的返回传入函数的异常退出码 如果活动对象能够处理 RunL()中发生的所有异常退出 它应该通过覆写缺省的实现CActive::RunError()来处理错误然后返回 KErrNone 如果在RunL()中不会发生异常退出,则不必覆盖

36 错误处理 如果 RunError()返回非 KErrNone 的值,表明异常退出尚未解决 活动调度器
没有任何可用于执行错误处理的关于活动对象的上下文信息 因此,通常更倾向于在相应活动对象的 RunError() 方法中进行错误恢复

37 析构活动对象 CActive派生类的析构函数应该总是调用Cancel() 析构代码 作为清除代码的一部分来终止选定的请求
理想的情况是在活动对象持有的其他资源被销毁之前完成该调用,因为这些资源可能会被服务提 供者或者DoCancel() 方法所使用 析构代码 应该释放对象持有的所有资源,包括所有异步服务提供者的句柄

38 析构活动对象 基类CActive的析构函数是虚函数 该致命错误捕获所有导致它的程序设计错误 再验证确认活动对象没有已提交的请求被选定后
对对象进行检查,确保活动对象当前不是活动的 如果有任何请求被选定,即 Cancel() 没有被调用,那么将会发生致命错误 该致命错误捕获所有导致它的程序设计错误 如一个请求在处理它的活动对象被销毁后完成了 这会导致一个“迷失信号” (“stray signal” ),这时活动调度器无法找到一个活动对 象来处理事件 再验证确认活动对象没有已提交的请求被选定后 CActive 析构函数会将活动对象从活动调度器中移除

39 活动对象类的示例 下面的例子 描述了如何用活动对象包装异步服务 这个例子中采用的是 RTimer 服务提供的一个定时器 Symbian OS 已经提供了一个抽象活动对象类 CTimer 该类包装了 RTimer ,并 且可以继承该类 但是,这里还是使用本例,因为它用简单明了的方式展示了如何写一个活动对象类 下面的幻灯片 展示了涉及到的类以及它们和活动调度器的关系

40 活动对象类的示例 CActive CActiveScheduler CExampleTimer RTimer iActive iStatus
RunL() DoCancel() RunError() CExampleTimer NewL() After() CActiveScheduler Add() ... RTimer n 1 iTimer CExampleTimer 及其与 RTimer, CActive 和 CActiveScheduler之间的关系 Due to powerpoint limitations, the UML does not accurately show the relationship between CActiveScheduler and CActive (it is aggregation and not composition). The same with CExampleTimer and RTimer. See the ASD Primer chapter 9.

41 CExampleTimer 类 class CExampleTimer : public CActive { public:
static CExampleTimer* NewL(); void After(TTimeIntervalMicroSeconds32& aInterval); protected: CExampleTimer(); void ConstructL(); // 两阶段构造 virtual void RunL(); // 从CActive继承 virtual void DoCancel(); virtual TInt RunError(TInt aError); private: RTimer iTimer; TTimeIntervalMicroSeconds32 iInterval; };

42 CExampleTimer 构造函数 CExampleTimer::CExampleTimer() : CActive(EPriorityStandard) { CActiveScheduler::Add(this); } void CExampleTimer::ConstructL() { User::LeaveIfError(iTimer.CreateLocal()); } CExampleTimer* CExampleTimer::NewL() {...} CExampleTimer::~CExampleTimer() Cancel(); iTimer.Close(); // 关闭句柄 创建异步服务提供者 第二阶段构造,出于整洁,略去代码

43 CExampleTimer::After()
void CExampleTimer::After(TTimeIntervalMicroSeconds32& aInterval) { if (IsActive()) _LIT(KExampleTimerPanic, "CExampleTimer"); User::Panic(KExampleTimerPanic, KErrInUse)); } iInterval = aInterval; iTimer.After(iStatus, aInterval); SetActive(); 同时只允许提交一个定时器请求,调用者在提交另一个请求之前必须调用Cancel() 设置 RTimer 将该对象标志为活动的

44 RunL() 和 DoCancel() void CExampleTimer::RunL() {
事件处理方法 如果发生错误就退出并且在 RunError()中处理错误 否则, 标记完成时间 提交计时请求 撤销定时器 void CExampleTimer::RunL() { User::LeaveIfError(iStatus.Int()); _LIT(KTimerExpired, "Timer Expired\n"); RDebug::Print(KTimerExpired); iTimer.After(iStatus, iInterval); SetActive(); } void CExampleTimer::DoCancel() iTimer.Cancel();

45 错误处理和事件处理 如果没有发生错误 一旦定时器请求已经开始,它将持续运行并且不断被提交
事件处理者RunL() 使用RDebug::Print() 记录定时器完成情况作为调试输出 RunL() 用存储的间隔值重新提交计时器请求 一旦定时器请求已经开始,它将持续运行并且不断被提交 直到活动对象调用Cancel()方法将其停止

46 错误处理和事件处理 事件处理者RunL()负责检查活动对象的iStatus 结果 在这种情况下 –错误处理十分简单:
如果 iStatus 的值不是KErrNone 就异常退出,这样RunError() 方法可以处理发 生的问题 在这种情况下 –错误处理十分简单: 从请求返回的错误被记录作为调试输出 这可能在RunL() 方法中已经完成 但是这被分离到了RunError() 方法中,用来演示如何使用活动对象框架将错误处理 从时间处理的主逻辑中分离出来

47 CExampleTimer::RunError
该函数在 RunL()发生异常退出时被调用(aError 包含一个异常退出码) 记录错误已被处理 TInt CExampleTimer::RunError(TInt aError) { _LIT(KErrorLog, "Timer error %d"); RDebug::Print(KErrorLog, aError); return (KErrNone); }

48 活动对象 活动调度器 了解活动调度器的角色和属性
知道 CActiveScheduler::Start() 只能在至少有一个活动对象请求发出的时 候之后才能被调用 明白线程处理事件失败的典型的原因是可能是活动调度器没有启动或者过早的停止 了 理解 CActiveScheduler 可以称作继承,以及创建一个派生活动调度器的原因

49 建立和安装活动调度器 绝大多数线程都有活动调度器
它通常是由框架隐式创建的 (例如 GUI 框架中的CONE) 服务器代码必须在使用活动对象之前显示创建并启动一个活动调度器 基于控制台的测试代码如果依赖了一些使用活动对象的组件,必须在其主线程中创建一个活 动调度器 GUI framework is not covered by this course but may be discussed in examples

50 建立和安装活动调度器 建立和安装活动调度器的代码:
CActiveScheduler* scheduler = new(ELeave) CActiveScheduler; CleanupStack::PushL(scheduler); CActiveScheduler::Install(scheduler); GUI framework is not covered by this course but may be discussed in examples

51 启动活动调度器 一旦活动调度器被建立并且安装完毕后,它的事件处理的循环等待功能将通 过调用静态CActiveScheduler::Start() 启动 但是调用Start() 进入事件处理循环后,只有在调用CActiveScheduler::Stop() 的时候才会返回 在活动调度器启动之前,必须至少有一个活动对象发出的异步请求 这样,线程的请求信号量被标识并调用User::WaitForAnyRequest()来完成 如果没有请求发出,线程只是简单的进入循环等待状态并且不定时的休眠

52 活动调度器等待循环 当异步请求完成时 它传递给RequestComplete()
当服务提供者和服务请求者在同一个线程中的时候,异步服务提供者调用 User::RequestComplete()方法 当它们不在同一个线程中的时候,调用 RThread::RequestComplete() 方法 它传递给RequestComplete() 与请求关联的TRequestStatus 一个完成结果 典型的标准错误代码,如 KErrNone 或者 KErrNotFound RequestComplete() 将TRequestStatus 值设定为给定的错误代码 在请求线程中产生一个完成,通过标志线程的请求信号量

53 活动调度器等待循环 当请求已经发出,请求线程将在活动调度器的事件处理循环中运行
当它没有处理其他的完成事,活动调度器通过调用User::WaitForAnyRequest()挂起 线程 该函数将等待线程的请求信号量的标记信号

54 活动调度器等待循环 当收到信号时 活动调度器决定哪个活动对象来处理该事件 它检查活动对象的优先级列表,然后决定标记哪个请求
即,把他的iActive 的布尔值设为ETrue (在请求被提交并且调用 CActive::SetActive() 之 后设定) 活动调度器检查活动对象的TRequestStatus 成员数据,来确定是否设置了一个除 KRequestPending之外值 指明活动对象与一个已经完成的请求关联,并且应该调用其事件处理代码

55 活动调度器等待循环 找到合适的活动对象 RunL() 处理事件 RunL() 完成
活动调度器清除活动对象的iActive 布尔标记,并且调用RunL() 进行事件处理 RunL() 处理事件 进行必要的处理 它也可能提交一个请求或者在另一个系统中生成一个对象 注意: 在运行的时候可能产生另一个事件的请求,但是RunL() 不能被抢占 RunL() 完成 活动调度器恢复控制权 然后决定是否有其他请求已经完成

56 活动调度器等待循环 一旦 RunL()完成后 User::WaitForAnyRequest()
检查线程的请求信号量 没有线程请求完成: 挂起它 如果在以前的RunL()还在运行时,信号量指出另一个事件已经发生:则立刻返回,重复活动对象查找和错 误处理

57 活动调度器等待循环 事件处理循环伪代码 EventProcessingLoop() { User::WaitForAnyRequest();
FOREVER if (activeObject->IsActive())&& ActiveObject->iStatus!=KRequestPending) activeObject->iActive = EFalse; TRAPD(r, activeObject->RunL()); if (KErrNone!=r) r = activeObject->RunError(); Error(r); } break; 挂起线程直到事件发生 从优先级列表中取得下一个活动对象 并且事件的 iStatus!=KRequestPending 找到一个已准备好处理事件的活动对象 重置 iActive 状态以表明其不再活动 在TRAP中调用活动对象的事件处理 事件处理函数异常退出,调用RunError() 不使用objectRunError()处理错误 调用CActiveScheduler::Error() 事件处理完毕,跳出查找循环并在FOREVER 循环结束处恢复执行 1. 当请求信号亮被通知的时候,线程将苏醒 2. 按照优先级降序的顺序检查调度器器中每一个活动对象 3. 调用第一个活动且已完成的活动对象的事件处理函数 The pseudo-code above does not show what happens if the active scheduler inspects all the active objects in its list but cannot find one with an outstanding request and iStatus value indicating that it has been completed by the asynchronous service provider. If this occurs, the active scheduler raises a “stray signal” panic (E32User::CBase 46). Some reasons for this to occur are described later.

58 停止活动调度器 活动调度器停止 当调用CActiveScheduler::Stop()方法完成,即返回时 停止活动调度器
通过调用 CActiveScheduler::Stop(), 通常在RunL()中调用 当调用CActiveScheduler::Stop()方法完成,即返回时 对CActiveScheduler::Start() 的调用也返回 停止活动调度器 终端线程中的事件处理 它只应被控制线程的主活动对象调用 因此在 GUI 应用中不要这么做

59 扩展活动调度器 CActiveScheduler 是一个具体的类
它可以用于 “就是” ,也可以被继承 它定义了两个可扩展的虚函数:Error() 和WaitForAnyRequest() 默认情况下WaitForAnyRequest()函数只是简单的调用 User::WaitForAnyRequest() 但它可以被扩展,例如在等待之前或之后执行一些处理 如果函数被覆盖,它必须要么就必须调用基类函数或者直接调用 User::WaitForAnyRequest()

60 扩展活动调度器 如果在 RunL() 事件处理函数中发生异常退出 如果 RunError() 方法无法处理异常退出
E32USER-CBASE 47 但可以被覆盖以处理错误 例如,通过调用错误解析器来获得错误的文本描述,并将这些内容显示给用户或写入日 志文件中

61 警告 如果活动对象代码 此外 依靠特殊的活动调度器的特殊版本 那么就无法移植这些代码使其能运行在其他由更基础的活动调度器管理的线程中
为了扩展活动调度器而添加的代码应该简单易懂 并且要避免执行复杂或者缓慢的处理而妨碍整个线程的事件处理

62 没有活动调度器的线程 有一些线程有意的不使用活动调度器,因此它不能使用活动对象或者由活动对象组成 的组件 Java实现 不支持活动调度器
C 标准库 (STDLIB)线程是没有活动调度器的,因此标准库代码不能使用活动对象。但表准库代码 可被活动对象所使用,例如在初始化代码中或者在RunL() 中 OPL不支持活动调度器,并且OPL的C++扩展 (OPXs) 绝不能使用活动对象或者那些使用活动对象 的组件 OPL 是一个解释性语言,使用入门级的开发工具,可以进行快速应用开发

63 活动对象 取消已发出的请求 理解活动对象在正常完成异步请求以及调用Cancel()结束时,代码中的不同路径

64 CActive::Cancel() CActive::Cancel() 活动对象内部 调用 DoCancel()的基类

65 撤销一个已发出的请求 当调用 CActive::Cancel()时发生了什么? 首先检查调用它的活动对象是否有标记过的请求
这通过检查iActive 标记是否通过调用CActive::IsActive()函数来设置过

66 撤销一个已发出的请求 如果活动对象确有已发出的请求 实现 DoCancel() 时
CActive::Cancel() 调用 DoCancel() – CActive类中的纯虚函数 必须在活动对象类中实现 实现 DoCancel() 时 不需要有代码来检查是否有发出的请求 因为如果没有发出的请求,DoCancel() 不会被调用 通过调用其提供的撤销方法, DoCancel()必须发送给撤销封装的异步服务提供者的请求

67 撤销一个已发出的请求 调用了 DoCancel()以后
CActive::Cancel() 然后调用 User::WaitForRequest() 传递关于他的 iStatus成员 变量 CActive::Cancel() 是一个同步函数 直到DoCancel()返回并且原来的异步请求完成时它才会返回。 因此: DoCancel() 不应该执行长操作 线程被阻塞直到异步服务提供者发布一个取消通知,即将 iStatus设置为KErrCancel CActive::Cancel() 重置活动对象的iActive 成员,以显示不再有已发出的请求了

68 撤销一个被发出的请求 撤销事件 CActive::Cancel() 方法执行所有普遍的撤销代码 一个派生活动对象类
RunL() 不会被调用 CActive::Cancel() 方法执行所有普遍的撤销代码 一个派生活动对象类 只用DoCancel() 来调用异步服务提供者的适当的撤销函数 并且执行必要的清除操作 DoCancel() 不可以调用 User::WaitForRequest() 这将使得线程的信号量计数变得混乱

69 信号迷失的致命错误 当一个活动对象要被销毁的时候,必须确保没有等待完成的需要处理的事件
这是因为CActive的析构函数把活动对象从活动规划器列表中删掉了 如果任何标记的请求要在过一段时间才能完成的时候进行销毁,将会产生一个活 动对象失去关联的事件 这将导致迷信号的致命错误 More about stray signal panics in a later slide

70 CActive::~CActive() 避免信号迷失错误
要强调这个问题,将抛出一个 E32USER–CBASE 40 致命错误 这个致命错误比迷失信号的致命错误更容易跟踪 鉴于这个原因, Cancel()应该在每个继承自活动对象类的对象中被调用

71 活动对象 后台任务 理解如何使用活动对象来完成长任务(或者说是后台任务) 理解如何实现自我完成(self-completion)

72 后台任务 除了封装异步服务提供者 活动对象也可以用来实现长任务,这种任务需要在低优先级的后台中运行 任务必须能分割为若干小的片段
例如,为打印准备数据,执行后台重算和压缩数据 这些都要在活动对象的事件处理方法中执行 它们必须足够短,因为RunL()一旦运行就无法被抢占

73 后台任务 活动对象应被赋予一个低优先级 如果任务由一系列不同的步骤组成
诸如 CActive::TPriority::EPriorityIdle (=-100) 决定了任务片段只在没有 其他事件要处理的时候才运行 也就是通常所说的间歇运行 如果任务由一系列不同的步骤组成 活动对象必须跟踪运行的一系列状态 使用状态机实现

74 后台任务 活动对象 活动对象通过产生自己的事件来调用自己的事件处理函数,从而可以驱动任务
不再调用异步服务提供者,只是活动对象对自己的iStatus 对象调用 User::RequestComplete() 这样活动对象就将调用它的事件处理函数 这样的话,活动对象就会不断提交请求,直到整个任务完成

75 后台任务 示例代码中展示的是一个典型的例子 在类声明中展示了所有相关方法 但只给出了和这里的讨论相关的实现 为了简洁,这里省略了错误处理
StartTask(), DoTaskStep()和 EndTask() 执行小的、不连续的任务,可以直接 被低优先级活动对象中的RunL()方法所直接调用

76 后台任务: CLongRunningCalculation
class CLongRunningCalculation : public CBase { public: static CLongRunningCalculation* NewL(); TBool StartTask(); TBool DoTaskStep(); void EndTask(); ... }; TBool CLongRunningCalculation::DoTaskStep() } 在任务执行前初始化 执行一个短小的任务步骤 销毁中间数据 执行一个尖酸的任务步骤,如果还有任务要做则 返回 ETrue, 如果任务完成了 ,要返回 EFalse 略去

77 后台任务: CBackgroundRecalc 活动对象
class CBackgroundRecalc : public CActive { public: ... void PerformRecalculation(TRequestStatus& aStatus); protected: CBackgroundRecalc(); void ConstructL(); void Complete(); virtual void RunL(); virtual void DoCancel(); private: CLongRunningCalculation* iCalc; TBool iMoreToDo; TRequestStatus* iCallerStatus; }; CBackgroundRecalc::CBackgroundRecalc():CActive(EPriorityIdle) CActiveScheduler::Add(this); } 出于简洁,略去 NewL(), 析构函数等 iCalc 是长线任务—— 其他的活动 对象都有一个异步活动提供器 iCallerStatus 用来通知调用者任务完成 创建低优先级任务

78 后台任务: PerformRecalculation
CBackgroundRecalc 是一个高效的异步 服务提供者 调试机制 iCalc 初始化任务自完成以产生一个事件 Complete() 将 iStatus 置为完成状态 从而对自身产生一个事件 void CBackgroundRecalc::PerformRecalculation(TRequestStatus& aStatus) { iCallerStatus = &aStatus; *iCallerStatus = KRequestPending; _LIT(KExPanic, "CActiveExample"); __ASSERT_DEBUG(!IsActive(), User::Panic(KExPanic, KErrInUse)); iMoreToDo = iCalc->StartTask(); Complete(); } void CBackgroundRecalc::Complete() TRequestStatus* status = &iStatus; User::RequestComplete(status, KErrNone); SetActive();

79 后台任务: RunL & DoCancel void CBackgroundRecalc::RunL() { if (!iMoreToDo)
将后台任务分成小片段处理 在任务下次执行或停止时重新提交请求 不需再做了——事件已完成 允许 iCalc 清除所有中间数据 通知调用者 提交另一请求并将通过自完成来产生事件 DoCancel 给 iCalc 一个执行清除操作的机会 通知调用者取消操作已发生 void CBackgroundRecalc::RunL() { if (!iMoreToDo) iCalc->EndTask(); User::RequestComplete(iCallerStatus, iStatus.Int()); } else iMoreToDo = iCalc->DoTaskStep(); Complete(); void CBackgroundRecalc::DoCancel() if (iCalc) iCalc->EndTask(); if (iCallerStatus) User::RequestComplete(iCallerStatus, KErrCancel);

80 活动对象 常见问题 知道一些可能导致迷失信号,无回应的事件处理和线程阻塞的原因,

81 迷失信号错误 在编写活动对象代码时最常遇到的问题就是“迷失信号”致命错误 (E32USER-CBASE 46)
它会在活动调度器接收到一个完成事件却无法找到处理该事件的活动对象时产生 例如:一个当前处于活动状态并具有一个完成了的 iStatus 结果(由非 KRequestPending 的 值指示的)活动对象

82 迷失信号错误 下列原因都会产生迷失信号: 活动对象被创建的时候没有调用 CActiveScheduler::Add() 方法
当向异步服务提供者提交的请求被接受后 ,没有调用 SetActive() 异步服务提供者多次完成活动对象的 TRequestStatus 成员,要么: 因为异步服务提供者程序设计的错误,或者 因为在同一个活动对象上提交了不止一个请求

83 无回应的事件处理 比如说,在一个 UI 线程中使用活动对象进行事件处理时 活动对象应该是“协作的”而且不应该:
在活动调度器中,不应该有活动对象具有阻止其他活动对象进行事件处理的垄断权 活动对象应该是“协作的”而且不应该: 有冗长的 RunL()或 DoCancel() 方法 反复地再提交快速完成的请求,阻止其他活动对象来处理事件 赋予其比实际需要更高的优先级

84 线程阻塞 线程可以被阻塞,从而阻止一个应用程序的 UI 继续保持回应, 包括下面几条 的许多原因都可以造成线程阻塞:
调用 了User::After(),它将阻塞一个线程直到参数指定的一段时间过去为止 活动调度器的不正确的使用 在启动活动调度器之前, 至少必须有一个异步请求通过活动对象被提交,这样一来线程的请求信号量就会 被告知,同时 User::WaitForAnyRequest() 将完成 如果没有未完成的请求,线程就只是不确定的进入等待循环和休眠状态 使用 User::WaitForRequest() 等待一个异步请求,而不是使用活动对象框架

85 活动对象 Symbian OS 中的时间驱动多任务 CActive类 活动规划器 撤销标记的请求 后台任务 常见的问题


Download ppt " 活动对象(Active Object)."

Similar presentations


Ads by Google