我对active object的一点理解

 

作者:huwell     发表日期:2003年10月18日    阅读次数:428


--------------------------------------------------------------------------------


Active object是EPOC中最具特色的东西,它提供了非抢占式多任务(和传统的Unix系统一样)。从而使得多线程编程对大多数程序和服务来说不再是必要的了。

以往的系统中,多任务是靠线程来摆平的,而在EPOC中,我们使用active object来处理大部分的多任务,因为它更适合手持设备类的系统使用,我们得高效的利用这些有限的系统资源,因此我们得尽量减少线程的使用以及他们之间的交互。

要透彻理解active object,就得对多任务有充分的理解,多任务有几种类型,下面逐一来讲
1、长时间运算
如果是使用多线程那就可以很方便的解决,我们只需要关注运算即可,其他事务可以由CPU去处理。长时间运算应该很容易的被其他任务强占,因此它应该使用低优先级的线程。

2、事件处理
在实际系统中,主要的任务是事件的处理。他们花费了大量的时间来等待事件的发生,并准备响应它们。

3、real-time event handling
有些事件的响应需要在一段特定时间内完成,而忽略这段时间内系统的其他活动。
如用户切换程序,把当前程序放入后台,把要求的程序放入前台等
注意实时响应和快速的响应是不同的。系统设计者为了满足实时的要求而采用高优先级线程。

4、Animation and games
动画游戏可能需要实时去响应用户的控制,以及实时的显示画面,为了制作出精彩的游戏,游戏编程者或商家通常编写他们自己的API以满足这些要求。当然EPOC的window server也提供了一些动画特性以帮助使用在程序中。

在共享数据方面,EPOC的活动对象机制提供了非强占式多任务,因此提供了所有的活动对象在各自的RunL()去完成共享数据的自动更新,因此不需要锁定机制。活动对象并没有采用特殊的核心支持,他们可以利用传统的C++API进行控制。

有时候看见这样的代码,也就是当用户发出请求后,如按了某个键。这样的代码如下
Cancel();
iTimer.Afer(iStatues, aDelay);
SetActive();
注意最后的SetActive()非常之重要,它设定了active flag,这就确保active scheduler(是CActive的友类)能检查出active object的TRequestStatus成员——这是用来记录提交给服务器的请求完成状态的,这也是CActive封装的一个成员变量,因此派生类无须处理它们。
这时要将TRequestStatus设定为KRequestPending。
否则,active scheduler就认为active object并没有等待的请求,而不会给它处理请求的机会。这可能导致active scheduler会向thread报错。
而Cancle()函数可以防止万一已经有了等待的请求,有了的话就结束它而不等待其完成。

总的来说,也就是requester发送了消息,然后provider就会设置这个TRequestStatus
当request完成后,我们就必须来处理RunL()来提供service
这里没有必要去reset那个active flag,在调用RunL()之前,active scheduler就已经做了。


几乎EPOC中的所有代码都是在active object的RunL()的控制下执行的。每个程序,和每个系统服务,都由EPOC分配了属于它们自己的线程,但由于有active object,大部分情况下我们不再需要增添额外的线程。
在调用RunL()之前,active scheduler会marked this active object’s request as complete (i.e. the request is no longer outstanding)
当请求的服务完成后,provider就会调用RequestComplete(),指明TRequestStatus为KErrNone或其他错误代码,以及一个完成代码,这将导致:
1、请求线程的request semaphore被发送,允许线程继续执行——如果它正在等待的话。
2、其TRequestStatus()设置为完成代码值。

如果server provider在不同的线程里,那需要一个RThread来指明requester的线程,它会使用RThread::RequestComplete()来发送完成到正确的线程。如果在同一线程,又扮演着requester的角色——由于active object的性质,这是很常见的情况。它就必须通知它自己,在这样情况下,只要调用静态函数User::RequestComplete()函数就可以了。

 

一个线程可以等待一个特定的请求完成,使用如下的代码:
RFile file;
TRequestStatus readStatus;
...
file.Read(buffer, readStatus); //issue request
User::WaitForRequest(readStatus); //wait for completion

如上的代码很少见,因为这将意味着线程会等待请求的完成而不做任何其他工作。因此一般很少这样使用。
最佳的使用方法,是在处理cancellation时,因为Cancel()函数必须立即返回,因此
RTimer timer;
TRequestStatus timerStatus;
...
timer.After(timerStatus, 10*1000000); //completes after 10 seconds
...
timer.Cancel(); //returns immediately
User::WaitForRequest(timerStatus); //do required wait


也就是
EXPORT_C void CActive::Cancle()
{
  if(!iActive) return; //forget it, if no request outstanding
  DoCancle(); //let derived class cancel the service provider
  User::WaitForRequest(iStatus); //wait for request to complete
  iActive = EFalse; //no request outstanding now
}
对派生类来说,我们所要做的就是实现自己的DoCancle(),它最好是做到确信没有任何等待的请求。以免删除对象时出错。

活动对象的析构函数就应该调用Cancle()来确保没有等待的请求了。
而CActive的析构函数做什么那?它会确保活动对象在被析构之前已经从active scheduler中删除了。


在EIKON程序,很容易使用上active object,最好的设置位置就在程序的app UI,在它的构造函数中生成对象,在析构函数中清除
void CExampleAppUi::ConstructL()
{
...
iBoo=new (ELeave) CDelayedBoo; // allocate
iBoo->ConstructL(iEikonEnv); // second phase construct
}
CExampleAppUi::~CExampleAppUi()
{
delete iBoo;
...
}

Incidentally, objects involved in asynchronous processing should not normally be allocated on the stack: this is possible above only because of synchronous waiting. Objects involved in asynchronous processing need to have a lifetime that spans the completion of a request — whether normally or by cancellation. This requires that they be allocated on the heap, or on a stack frame which encloses the entire program (rarely possible, except for system components such as EIKON which set up the program’s infrastructure).

很多情况下,一个单独的线程都会处理多个等待请求,线程必须正确的处理这个问题,因此就需要如下合适的方法:
1、使用User::WaitForAnyRequest()来等待所有请求的完成: this will either suspend and then resume when the first request completes — or, if one or more requests have already completed, this function will immediately return

2、search through TRequestStatuses associated with requests that have been issued, but not yet handled, looking for one that is not still set to KRequestPending

3、when the first such TRequestStatus is found, handle its completed request (and mark the request as no longer issued)

4、go back to (1)
EPOC将上述的等待循环封装在CActiveScheduler中了,每个EPOC线程都可以有一个active scheduler。active scheduler是由EIKON environment和server framework所提供。


有时候active object也可以管理自己异步服务的请求,并适当的处理它们,例如,在要求显示动画时,就可能如下设计:
·the request function handles the first part of the animation, and then sets a timer and returns
·the RunL() function, which handles completion of the timer request, does some more animation and then, if necessary, sets the timer again — but, if the animation has finished, the timer is not set again
·the DoCancel() function cancels the timer, and does no further animation

对active object的优先级来说,一般的event-handling活动对象其优先级都是设为0,user input可以把优先级设高一点。而long-running task active object的优先级通常设为CActive::EPriorityIdle(等于-100),虽然它的目的是要在event-handling active object的空闲时间去运行,但其实它是非常繁忙的:)


我们这里可以回顾一下active object的用途了

active object在symbian中的作用很大,它是非强占式多任务的基础,可以完成一般系统中线程所扮演的角色,它最主要的特性是封装了消息的处理和完成(如iActive和RunL),和active scheduler配合在一起,完成了很好的异步操作,关键是TRequestStatus,如果它是KRequestPending状态,那其他的消息就要等待,active scheduler,做为CActive的友类,它既接受了用户的请求,又检查了active object的状态,在适当的时候可以指挥它去完成服务。
这句话最能说明CActive,EPOC encapsulates the general-purpose behaviour of an event requester and handler into a single abstract base class, CActive.


接下来谈谈具体active object和scheduler的运作流程:
当向活动对象发送出一个请求时,这时会附带上TRequestStatus以标记该请求的完成状态。活动对象会将它标记为KRequestPending,以阻止其他请求的再来。然后等待请求的完成。如果完成后会使用CompleteRequest来标记TRequestStatus为完成状态的。
这个时候scheduler已经start()了,它会检查请求是否完成(通过WaitForAnyRequest等),如果完成了就会调用相应active object的RunL()函数来执行。
这里面还有个iActive,active object的派生类必须在发送出请求后调用它,以表示已经有个等待的请求了,这样scheduler才会去检查active object的iStatus(ie.TRequestStatus)来判断请求是否完成。而在调用RunL()之前,这个active flag已经被置原了。

 

为了帮助理解,这里再附上一篇文章摘要
下面摘自〈Windows程序员如何过度到symbainOS程序员〉一文
[消息,异步服务和活动对象]

MFC在对无论是初级还是了解驱动他们应用程序的消息循环和调度机制的高级程序员隐藏Windows消息范例方面做得很好。在Symbian 操作系统中,驱动应用程序的是一套完全不同又十分强大的机制,被称为活动对象。它与Symbian操作系统的客户端-服务器端结构紧密结合,提供了各种系统服务,同时也可以为程序员来给他们可能需要为应用程序创建的任何异步服务建立干净、标准的接口。

简而言之,CActiveScheduler执行了Windows消息循环,在一个给定线程内提供了共用的多任务,CActive(一个活动对象)充当了消息处理者。在Symbian 的UI 框架中,这要比MFC 把消息隐藏得更好:接收到的消息总是由框架调度给预先定义的可以被应用程序重载的方法——比如OfferKeyEventL(处理键盘输入)。

定时器是使用了活动对象框架的简单系统服务的一个实例。不同于Windows 中调用StartTimer 来触发WM_TIMER 消息的做法,Symbian 操作系统中是由一个CTimer 对象与系统时钟服务进行交互,RTimer 与其RunL 函数根据由API 规定的时间间隔被调用。程序员从CTimer 继承到一个对象,并重载RunL 方法。

其它的异步系统服务,如Nokia 7650 上的照相机功能也是通过类似方法提供的。CCameraManager 定义了一个活动对象提出拍照操作的请求并随即获得通知。实际的异步服务是由RCameraServ 提供的,但是典型的程序员根本用不着处理任何客户端-服务器端或者显式的进程间通信问题。

使用活动对象时,头脑中时刻牢记活动对象何时按预定计划执行这个很简单的准则非常重要。这就是说,到底什么时候CActiveScheduler 会调用一个CActive 对象的RunL 方法呢?

·CActive 对象必需是“活动”的。这是通过调用Cactive::SetActive 实现的,通常在活动对象自己对应用程序某个请求做出的响应中完成。

·CActive 对象的状态,由成员变量iStatus 所示,一定不能为KRequestPending。这个值表示该对象正在等待服务完成。通常在提出一个服务请求时变量iStatus 才会被服务提供者设为KRequestPending。iStatus 随后在服务结束时被服务者提供者更新。

·CActiveScheduler 需要接收到一个信号——该信号由异步服务在结束时发出。

当异步进程对应用程序发出结束信号,然而没有准备好的CActive 对象时,就会产生一个“迷失”信号。这在以下几种情况下可能发生:

·你忘记了使用CActive::SetActive 使活动对象处于“活动”状态。
·服务提供者在发出操作完成信号时忘记将iStatus 置为非KRequestPending 的值。这只会在你写了错误的服务提供者时出现——典型的应用程序会使用系统定义的服务。
·服务提供者给同一个操作发出了不只一个终止信号。

第三种情况在服务提供者设计不够仔细的情况下可能发生。例如,有一个服务已经结束并且对客户端通知了这一事实,但是客户端恰恰在收到这个通知之前发出了取消服务的请求。服务提供者必需忽略这一当前不合理的请求。要是服务提供者没有忽略这一请求,而是对它做出响应并给出一个附加信号说已经结束了,会是怎么样呢?在这种情况中,附加的信号最终就会成为“迷失”信号,并会引起程序出错。这对于纠错来说很困难,因为“迷失”信号是被CActiveScheduler 检测到的,没有显示是什么CActive 对象的责任。

CActive 对象必需确保在各种情况下与服务相关联的终止信号都恰当。在解除程序中应该典型的具有一个CActive::Cancel 调用。如果Cancel没有被调用,而对象在有请求仍被挂起时就被删除,错误就会发生。而且,每个CActive 对象必需以确保任何由该对象请求的服务都被取消的方式执行CActive::DoCancel,否则CActive::Cancel 就会一直等待服务提供者的终止信号。这两种错误都很难被检测到,因为它们只在存在明显的请求时才会被发现。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值