Symbian OS:活动对象与活动调度器

来源:百度文库 编辑:神马文学网 时间:2024/04/28 07:45:02
活动对象和一个活动调度器共同提供了非抢先式多任务,可用于替代Symbian OS中的多线程。使用活动对象可获得的好处是:与使用线程相比较,它编程简洁,且CPU的效率更高。
Symbian OS v8.0中引入了两个新机制,即发行预订和消息队列。它们使线程间和进程间的信息共享更为方便。它们并不提供除线程和活动对象以外的其他选择,但却可能对运行于多线程环境中的客户端代码产生结构决策方面的影响。
多任务是用在操作系统中的一项技术,以便在几个独立任务间共享一个处理器[ODICT,多任务]。
在协同式多任务中,正在运行的任务将决定什么时候放弃对CPU的使用,而在抢先式多任务中,当前任务运行了一段被称为“时间片”的固定时间段后,被一个名为“调度器”的系统进程挂起。在这两种情况下,调度器负责选择下一个要运行的任务,并(重)启它。
多线程意味着:以一种意在最小化线程切换所需时间的方式在多个任务(或“线程”)间共享一个单一的CPU。这是通过在不同的线程间共享尽可能多的程序执行环境实现的,从而当切换线程时,只需要极少的状态储存和恢复。[ODICT,多任务]。
Symbian OS实现了抢先式多线程,因此它能同时运行多个应用和多个服务器。一些活动对象用于在某个单一线程中实现非抢先式多任务。
线程间的调度由Symbian OS自动完成。程序员只是创建一个线程,并将其设置为能运行所需的任务。系统调度器向每个线程自动分配CPU时间。线程间的切换如此频繁,以至一个线程的执行看上去是连续的。
某个线程可以有一个活动调度器,它调度各活动对象间的执行时间(为该线程分配的时间片)。可以把活动对象视为线程的子任务。然而,活动调度器在调度过程中使用了一个非抢先方法:正在执行中的活动对象不会被中断(如同线程执行一样)。当活动对象完成其任务并把控制权交还给活动调度器时,它自己会做出决定。然后,活动调度器就可以在执行过程中选择另一个活动对象。
在很多情况下,线程中都安装有一个活动调度器。在这种情况下,程序员可以立即使用活动对象。例如,用户界面(UI)应用(.app)有主线程,主线程有一个活动调度器。然而,当写服务器(.exe)或创建自己的线程时,必须在这些活动对象能被使用之前先安装这个活动调度器。
活动对象的典型用法是:在某个应用中处理一些事件。例如,应用框架使用一些活动对象来处理按键和屏幕更新。处理该事件的活动对象往往会发起一个对已被该程序员所重置的方法(例如leKeyEventL())的回调。当这类方法被重置后,需要理解的是:该方法是由某个活动对象所调用的,这意味着,在该方法返回之前没有其他活动对象可以运行。
客户端/服务器结构和异步方法
Symbian OS具有一个先进的微内核结构,它使用客户端/服务器结构模式。为Symbian OS编写的那些应用通常是通过服务器使用各种资源的客户端。该服务器往往提供一个R-类,这是一个针对各种服务器服务的代理。
各种R- classe具有两种方法:
1. 异步方法。某个客户端线程调用该方法,它构造一则消息并将其发送到服务器线程。当发送消息之后,该客户端线程将从该方法中返回。
服务器端有自己可接受各种消息的线程。它读取所传递的数据并处理请求。当服务器处理完请求后,它会对该客户端线程发出提示:该请求已完成,并传递一个作为结果的完成代码。
客户端线程必须处理这种提示和结构代码,但可以在某个适当的时间进行。
2, 同步方法。消息传输和服务器处理过程类似于异步方法,但是调用R-类方法的客户端线程在消息发送之后并不返回。相反,它挂起该线程的执行,并等待来自服务器的有关该请求已完成的提示。
当服务器向客户端发送响应时,Symbian OS会去唤醒被挂起的客户端线程。然后客户端线程会去处理那个完成代码,此时如果完成代码显示服务器端有错,就会去处理这些错误,并返回。
当调用某个同步方法时,客户端线程会被阻塞,直到服务器线程向其提示:该请求已经完成。在许多情况下,客户端程序必须更新其UI并处理用户输入事件。如果同步方式要用很长时间完成,用户体验将由于这些事件得不到处理而受到损害。
当程序使用一些异步方法时,客户端线程能处理这些事件并更新UI,而服务器则会完成该请求。当服务器处理完请求后,客户端将得到有关请求已完成的提示。该客户端以处理其他事件的相同方式处理该提示。在这种情况下用户体验将非常良好,因为程序会响应用户的行为,从而使一切看上去比较顺利。
使用一些同步方法要比使用异步方法更为容易。当使用某个异步方法时,调用程序必须记住对每一个异步方法的调用,并追踪其是否完成。当同步方式返回时,其结果可马上得到处理,因此就没有必要追踪那些请求的完成情况。
然而,Symbian OS提供了框架,该框架使各种异步服务的使用更为便捷:活动对象和活动调度器。使用异步接口,而不是同步接口的原因之一就是效率和良好的用户体验。
区别异步方法还是同步方法是很容易的——异步方法必须具有TrequestStatus作为其参数之一。这就是处理请求后,服务器储存完成代码所处的值。
各种同步方法并没有这种参数,因为它们在从该方法返回前等待其完成,并对结果进行处理。各种异步方式的优秀范例是:RFile中的一些读写方法:
void RFile::Read(TInt aPos, TDes8& aDes,
TRequestStatus& aStatus) const;
void RFile::Write(const TDesC8& aDes,
TRequestStatus& aStatus);
RFile还提供来自某些相同方法的实现。它们的声明都相似,但是它们不把TRequestStatus作为一个参数:
TInt RFile::Read(TDes8& aDes) const;
TInt RFile::Write(const TDesC8& aDes);
活动对象和活动调度器
动机
事件驱动环境中的一些任务(如各种UI应用中的一些任务)处理来自系统的一些事件。程序代码往往调用某个异步方法,它处理一个请求。当该请求完成之后,客户端得到提示,处理结果,且很有可能发起一个新的请求。
如果用一些线程来实现某些任务,针对每一个事件源都必须有一个线程。例如,某个线程可处理键按下。当按下某个键后,它可能会去请求服务器发出一个提示。然后该线程就会被挂起,直到服务器唤醒该客户端线程去处理那次按键。同时,可能还存在着另一个线程,它以某个异步(或阻断同步)方法以某种类似方式从socket读取数据。
各种活动对象提供了事件处理的便捷方式。当发出一个异步请求并当服务器完成该请求时,活动对象的内部属性得到了传递,以取得响应代码。活动调度器关注该活动对象的运行,及当该请求在服务器端得到完成后对结果的处理。
有很多理由解释在Symbian OS中为什么用活动对象,而不是线程,来处理各种事件。例如:
1. 线程间的通信比活动对象间的通信缓慢且困难些(活动对象在同一个线程中运行,因此它们不需要任何特殊方法来共享数据)。
2. 线程间的场景切换消耗CPU周期,从而使其性能表现下降。活动对象并不像线程那样会被中断,因此并不需要在各个切换间储存并恢复CPU及内存映射单元(Memory Mapping Unit,MMU)的状态。
3. 许多资源不能在线程间共享。由于活动对象用一个线程运行,它们可以共享资源。
4. 在线程间访问内存或访问其他共享内存通常需要对同步更新提供保护。这由信号量(semaphores)完成,这可能导致代码的复杂化,并造成性能下降。活动对象不用担心同步问题,因为它们是以某种非抢先方式调度的。
Symbian OS基于微内核结构。各种服务的使用都通过异步接口。活动对象和活动调度器针对异步服务的使用提供了方便的框架:某个活动对象发起一些异步请求,并处理该请求。有一个变量用于指出服务器何时已完成该请求。活动调度器追踪那些活动对象,当其发现有一个请求已完成时,它会调用所分配的活动对象的RunL()方法,而后者会去处理该服务器响应。
继承自CActive的某个类被称为一个活动对象。CActive被如下声明:
class CActive : public CBase
{
public:
enum TPriority
{
EPriorityIdle=-100,
EPriorityLow=-20,
EPriorityStandard=0,
EPriorityUserInput=10,
EPriorityHigh=20,
};
public:
IMPORT_C ~CActive();
IMPORT_C void Cancel();
IMPORT_C void Deque();
IMPORT_C void SetPriority(TInt aPriority);
inline TBool IsActive() const;
inline TBool IsAdded() const;
inline TInt Priority() const;
protected:
IMPORT_C CActive(TInt aPriority);
IMPORT_C void SetActive();
// Pure virtual
virtual void DoCancel() =0;
virtual void RunL() =0;
IMPORT_C virtual TInt RunError(TInt aError);
public:
TRequestStatus iStatus;
private:
TBool iActive;
TPriQueLink iLink;
friend class CActiveScheduler;
friend class CServer;
friend class CServer2;
};
通常, 活动对象用于发出一个异步调用,并当服务供应商处理完请求后去处理这些结果。流程如下:
1. 创建活动对象并将其添加到活动调度器中
2. 要求活动对象创建一个异步请求。该活动对象将其iStatus作为针对该异步方法的一个参数传递.这个异步方法将iStatus 变量设定为KRequestPending,以表示该活动对象正在等待某个请求的完成,并将请求消息发送给服务供应商。然后,异步方式返回。在活动对象的方法返回之前,它调用SetActive()方法,后者向活动调度器指出:必须追踪这个活动对象的完成。
3. 当服务供应商完成请求之后,它将结果代码赋予iStatus变量。
4, 活动调度器追踪被激活的活动对象。如果活动对象的iStatus不是KRequestPending,就调用其RunL()方法,因为服务器端的请求已经就绪。
5.   Method RunL() processes the result code of the previous asynchronous request.
方法RunL()处理先前的异步请求的结果代码。
上面讲述的范例十分简单:活动对象被用于仅完成一个异步请求。然而,RunL()本可以发出一个新的异步请求,激活自己,并等待,直到活动调度器再一次调用其RunL()。
为了正确使用活动对象,理解某些特性和行为是很有帮助的。
1. 状态。 当某个活动对象调用了一个异步方法,它将其iStatus作为参数传递。服务供应商立即将状态设定为KRequestPending,这表示:服务供应商正在处理该请求。
2. 活动性。当某个活动对象发出了一个异步请求,就必须以SetActive()方法对其进行激活。这是因为:活动调度器只对活动着的活动对象进行追踪。当活动对象被激活后,它将保持其活动状态直到活动调度器调用其RunL()方法或当前的任务已被删除时为止。
3. 优先权。活动调度器按优先权顺序追踪各个活动对象。针对活动对象的优先权在构造时给出。当活动对象没被激活时,可以用SetPriority()方法修改其优先权(这样某个请求就不再处于等待状态)。
4. 结果处理。当某个活动对象是活动的,且其iStatus不是KRequestPending,这意味着:服务供应商已经完成了该活动对象所发出的请求。当活动调度器发现这样一个对象时,它使这一对象不再活动并调用其RunL()方法。
方法RunL()是一个“纯虚”方法,因而必须在继承类中实现。异步请求的结果代码可以从iStatus参数中找到。该方法的实现几乎可以做任何事情,但不能持续太长时间!
注意:RunL()方法一定不能持续太久,因为活动对象是以非抢先方式调度的。同一线程中所有其他活动对象在RunL()执行期间被阻断。许多活动对象(如键盘事件处理器和屏幕刷新器等)需要快速响应这些事件,因此不能阻断太长时间。
1. 取消。活动对象正在执行的任务必须能被取消。当调用活动对象的Cancel()方法时,活动调度器调用该活动对象的DoCancel()方法,然后使这一对象不再活动。试图取消某个非活动的活动对象(因此无需等待任何请求的完成)不会产生任何效用
DoCancel()是一个“纯虚”方法,因此必须在某个具体继承类中实现。该实现必须取消当前正在等待中的异步请求。
具有异步方法的那些类往往具有用于取消那些等待中请求的方法。当调用这种取消方法时,服务供应商将取消当前请求并往往相应地把iStatus设定为KErrCancel。在客户端线程从这个被取消的方法中返回之前,它等待来自服务供应商有关已经取消该操作的提示。
2. 出错处理。活动调度器捕捉RunL()方法的执行。如果它出现了异常,活动调度器会调用该活动对象的RunError()方法并将某个异常代码作为参数传递出去。如果RunError()返回一个非零返回码,活动调度器将发出当前线程的资源紧缺警报。
RunError()方法是虚的,且可以被重置。默认实现返回作为一个参数传递的出错代码(因此活动调度器发出当前线程的资源紧缺警报)。
活动调度器
Symbian OS中的每个进程都有一个线程,即主线程。这个主线程为这个进程创建一些额外的线程。如果用到了活动对象,就必须为这个线程实例化活动对象调度器,然后启动它以便对活动对象进行调度。
Symbian OS的用户界面应用就是具有一个主线程的进程。应用框架为这个主线程安装一个活动对象。在活动调度器中还有许多的活动对象,他们处理应用事件(键盘按压和屏幕更新请求)并实现对应用程序员可能重置了的一些方法的回调。程序员可以使用由应用框架所提供的默认的活动调度器。
然而,如果程序员创建了自己的某个线程,默认情况下并不存在一个针对该线程的活动调度器。可执行程序(.exe)具有一个主线程,如各种应用一样,但是该线程并没有任何的活动调度器。要在这样的线程中使用活动对象,程序员必须安装活动调度器,运行它,然后删除它。
使用自己的活动调度器
要在某个不具备活动调度器的线程中使用一些活动对象,程序员必须:
1.      用方法CActiveScheduler::Install()为那个线程创建并安装一个活动调度器。
2.      创建活动对象并用方法CActiveScheduler::Add()将它们添加到那个活动调度器中。
3.      用这些活动对象发起一个或多个异步请求。
4.      用方法CActiveScheduler::Start()启动那个活动调度器。
该活动调度器的方法start()在其停止方法被调用前并不会返回。这就是为何在这个调度器中必须有一些活动对象来处理某些任务的原因。当这些任务得到处理之后,一个活动对象必须以方法CActiveScheduler::Stop()停止该活动调度器。然后早先被调用的该活动调度器方法CActiveScheduler::Start()返回。
在启动活动调度器之前其中必须有一些发出了一个异步请求的活动对象。这是因为:这个活动调度器只运行这样一些活动对象,这些活动对象的服务供应商已经提示其完成。当该活动调度器被启动时,如果并不存在任何等待中的未完成请求,这个线程将被永远挂起。
LOCAL_C void doExampleL()
{
// Create and install the active scheduler
CActiveScheduler* exampleScheduler=
new (ELeave) CActiveScheduler;
CleanupStack::PushL(exampleScheduler);
CActiveScheduler::Install(exampleScheduler);
// Create the service provider. Often, the
// service provider is part of the active object
CExampleServiceProvider* myServiceProvider=
new (ELeave) CExampleServiceProvider;
CleanupStack::PushL( myServiceProvider );
// Create the active object and issue the
// first asynchronous request
CExampleActiveObject * myActiveObject=
new (ELeave)
CExampleActiveObject( myServiceProvider );
CleanupStack::PushL(myActiveObject);
myActiveObject->IssueRequest();
// Now we can start the active scheduler
CActiveScheduler::Start();
// Remove the exampleScheduler and other
// objects from cleanup stack and destroy them
CleanupStack::PopAndDestroy(3);
}
首先,创建并安装活动调度器。然后,创建一个活动对象并将其添加到那个活动调度器之中。之后就用那个活动对象发出一个请求。活动调度器已被启动。活动调度器追踪那些活动对象,当其发现其中之一的请求完成时,它会调用其RunL()。
当这个范例活动对象决定该活动调度器应该运行这些活动对象时(例如,当程序行将终止时),它会调用该活动调度器的停止方法。然后该活动对象从其RunL()方法中返回,且该活动调度器发现:其停止方法已经得到了调用,并从其Start()方法中返回。