【IT168技术】有过PC编程经验,尤其是熟悉进程以及线程编程的朋友都知道,进程的调度有kernel来执行,其中包括进程资源的分配以及进程间的通信,这种和内核经常交互的动作会浪费非常大的额外开销。进程会创建至少一个线程,而且一个进程只有一个主线程,进程间的各个线程共享同一个进程下的资源,这样就是以一个进程为资源的分配单位。其他线程共享进程的资源,进行多线程的编程,这样减少了进程间通信的额外开销,但是线程间的上下文切换同样也会出现额外开销,只不过这些开销在PC机上显得微不足道。但是移动开发作为嵌入式开发的一份子,由于使用的是微内核,资源的处理不及PC机强大,而且本身的受到内存以及硬件资源的影响,移动设备不鼓励使用多线程开发,就Symbian而言他是用一个线程来模拟多线程执行多任务,从而节省了时间和空间。这是使用活动对象最大的好处。下面是有关活动对象的几个方面介绍:
一、异步方法和异步服务
提到Symbian独特的活动对象,首先是要从异步方法和异步服务说起。Symbian OS应用程序使用事件驱动的方式访问系统提供的异步服务,而活动对象是实现异步事件处理的最佳选择。所谓异步方法就是不需同步等待返回的结果,而继续执行应用程序的方法。异步方法本身不提供服务,只是发出请求,然后接着返回,等待服务提供者发出的服务完成的异步事件,直到接收到这个异步事件之后他才能知道服务到底有没有成功的完成。提供服务的是其他的应用程序,也就是所谓的服务提供者,他在完成服务后,会给服务提出者发送一个异步事件表明服务的完成情况。
打一个比方,一个客人去餐厅就餐,客人对服务员说他要点XXX菜,服务员就将客人的菜单交给了厨师,厨师来做菜。在这个过程中,客人发出了点菜的请求,而此时,客人没有必要完全不做任何的事情专心等待菜上来,他还可以看电视或者和朋友聊天等等。客人的请求就是异步请求,也就是前面讲的不必等到结果的返回再做其他的事情。厨师做菜就是异步的服务,菜做好了做个标记,然后给客人上菜,当然厨师也可以告诉客人这道菜没有做好,请客人再点其他的。
异步方法和同步方法相比,他的特点就是有一个 TRequestStatus &参数,这个参数是一个状态的标记,表明服务的完成状态,它的值由异步服务者设置,当接到服务请求时会把它的值设置成 KRequestPending,这个值表示未完成,如果当值设为 KerrNone 时表明请求已经成功完成,如果服务未完成就将值设为一个错误值。
在Symbian OS推荐的处理异步事件的方法有两种,第一种就是使用User::WaitForRequest()静态函数等待异步服务的完成:
1TRequestStatus status;
2AsyncFuntion(status);
3User::WaitForRequest(status);// 等待异步服务的完成
这个方法用的是操作系统的信号量机制,但是这个方法是把异步方法变成了同步操作,会阻塞应用程序。
第二种方法就是使用活动对象机制,以异步方式处理异步事件。这个方法的效率要比第一种高,关键是不会阻塞应用程序。
在单线程中,活动对象框架采用非抢占式多任务调度机制。一旦某个活动对象正在进行事件处理,其它活动对象的事件处理必须等到正在进行事件处理的活动对象运行完毕后才可以运行,即它是不可被抢占的。活动对象类必须直接或者间接的继承自CActive类,这个类被定义在e32base.h里面。CAcitve类是一个抽象类,具有两个纯虚函数,RunL()和DoCancel(),因此要想实例化这个活动对象必须要在子类中实现这两个纯虚方法。
1、构造活动对象
在类CAtive里面枚举TPriority定义了一组优先级的值。一般,优先级的值使用CActive::EPriorityStandard(=0),除非有其它更好的理由使用别的值。在构造函数里面,活动对象应该调用CActiveScheduler::Add()方法把它加到活动调度器里面。
1CMyActiveObject:: CMyActiveObject():CActive(CActive::EPriorityStandard)23 2 {45 3....67 4 }89 5void CMyActiveObject::ConstructL()1011 6 {1213 7 ....1415 8 CActiveScheduler::Add( this); //Add toscheduler1617 9 }1819
2、异步请求
使用活动对象的目的就是发送异步请求,由于每个活动对象只能有一个未处理的请求,因此对于请求的发送应进行严格的检查。通常每次发送请求后都要执行SetActive()方法,表明请求已经提交并且当前是未完成的,而调度器当检测出符合条件的活动对象后会将它的活动状态设为notactive,因此可以通过IsActive() 的返回值来判断是否有未完成的请求。如果有一个未完成的请求,还在接着发送就会出现Panic。
活动对象提交一个请求到异步服务提供者,会传递一个TRequestStatus成员变量(iStatus),服务提供者必须在开始异步服务之前把iStatus值设置为KRequestPending。
异步请求发送的过程图
3、取消异步请求
活动对象的基类CActive 提供了一个纯虚方法 DoCancel(),重载这个方法以实现取消请求的功能,但是需要注意的是应用程序以及活动对象本身都不能直接调用这个方法,而是应该调用Cancel()方法。因为Cancel()方法中首先检查活动标记,对于处于活动状态的活动对象调用DoCancel(),然后将活动标记设为EFalse。调用Cancel() 后如果请求被取消,服务提供者仍然会发送一个异步事件,并将状态标记的返回值设为KErrCancel。
4、异步服务完成后的处理过程
每个活动对象必须实现从基类CActive继承过来的纯虚方法RunL(),这是事件处理函数,当来自异步服务提供者的服务请求完成事件发生时,活动调度器就会选择调用相关活动对象的RunL()方法。
RunL()应该根据事件完成返回的code检查异步服务请求是否成功完成,这个返回值一个32位的整型值,也就是 TRequestStatus对象中的完成码(iStatus)。根据这个结果,RunL()会有不同的执行代码,其中有可能提交另一个请求或执行诸如写log文件之类的操作,以便将错误信息或重要的值输出到文件中。根据不同的要求,RunL()函数代码的复杂度差别很大。有一个关键的问题是由于RunL()开始执行后,它就无法被其他活动对象的事件处理程序所抢占。因此,RunL()其中的代码段应该尽可能短小以便能够很快地被执行完,这样其他的事件的处理才不会发生延误。
基类CActive还提供了一个重要的方法就是RunError(),他是一个虚方法,如果当RunL()发生Leave时,活动调度器会使用TRAP捕获此异步,然后调用这个方法。如果RunError()方法没有对异常做处理,那么调度器就要负责处理该异常。如果希望处理RunL()的leaves,应该重写CActive::RunError()这个方法处理异常。
活动对象调度器是指CActiveScheduler 类及其子类的对象。其实这个东西的作用通过命名就能看出,说的通俗一点就是用来管理活动对象的。大多数运行在SymbianC++上的线程都有唯一的一个活动调度器,它通常被框架隐式地创建并启动起来(例如,GUI框架的CONE)。他是用来循环检测活动对象的,一旦有符合条件的活动对象被检测到,就会调用其RunL()方法,使得活动对象对于异步服务的结果进行处理。在GUI程序中由于调度器已经被框架创建并安装好了,因此只需要将活动对象添加到调度器中,此外也无需手动的调用Stop()方法。
在控制台程序中使用调度器 CActiveScheduler有以下几个步骤:
1、创建调度器CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;
2、CleanupStack::PushL(scheduler);
3、安装CActiveScheduler::Install(scheduler);
4、构建活动对象,并add()到调度器中
5、活动对象发送异步请求
6、执行SetActive()方法激活活动对象
7、执行调度器的Star()方法启动调度器进行循环检测,找出优先级高的,活动状态时active的,完成状态status是KnotRequestPending 的活动对象执行其RunL()方法
8、调用Stop()方法停止调度器
1、阻塞应用程序
由于活动对象的运行是非抢占式的,一旦有活动对象的RunL()被执行,那么别的活动对象只能等待RunL()执行完成后才能执行,因此RunL()的代码段长度一旦过长或是执行长时间的运算,就会阻塞应用程序,造成延时。
2、信号的迷失
简单的说就是活动对象调度器收到了来自服务提供者的异步事件,但是找不到对应得活动对象来执行,就好比服务员从厨师那里端上顾客点的菜却找不到是哪个顾客的。出现E32USER-CBASE 46时表明是活动对象的迷失信号错误。导致这个错误产生的原因有以下几点:
活动对象没有添加到调度器中,却发送了异步请求。一旦异步请求发送成功,服务提供者就会接受该服务,并按照要求执行,因此在执行完成后,调度器却找不到对应的活动对象,造成信号的迷失。
活动对象发送异步请求后没有执行SetActive(),因此活动状态始终是 EFalse,因此调度器就找不到了。
活动对象在销毁之前没有调用Cancel(),以至于自己不存在了但是服务仍然进行,当服务完成后自然找不到对应的活动对象。
活动对象在活动状态时又接着发送请求,这样会有两个异步事件,由于活动对象处理完第一个后的状态已经被设为EFalse ,因此在第二个服务到来时却没有活动对象了。
总之,Symbian OS的 App使用活动对象机制来处理所有的异步事件,他是Symbian的一大特色,实际上应用程序中几乎所有的函数都是直接或者间接的被活动对象的RunL调用的。在Symbian的学习和工作过程中不可避免的会遇到关于活动对象的诸多问题,这些是参考和经验,要想更深刻的理解并用好活动对象必须要有自己的实践,希望大家能够掌握Symbian 中这一极具特色的东西,为自己的Symbian开发增添色彩。