随笔 - 2, 文章 - 73, 评论 - 60, 引用 - 0
数据加载中……

[转载]Active Scheduler & Active Object,活动规划器和活动对象

活动规划器 (Active Scheduler)

由于使用多线程来处理异步请求比较消耗系统资源,所以symbian 使用了活动对象(Active Object)来解决异步请求的问题。
活动规划器(active scheduler)用于处理由活动对象提出的异步请求。它检测活动对象提出的异步请求,并安排活动对象的请求完成事件的执行顺序。活动规划器仅用一个事件处理线程来规划各个活动对象提出的事件请求,所以它要比多线程实现异步请求占用更少的资源。
一个活动规划器通常对应到一个线程,CActiveScheduler 类的静态方法通常都是针对当前线程中对象的操作。
在程序中使用活动对象时,首先应该创建一个活动规划器对象,并把它安装到当前线程,也就是把活动规划器和当前线程关联。 CActiveScheduler::Install()方法可以把活动规划器安装到当前线程, CActiveScheduler::Current() 方法可以获取和当前线程关联的活动规划器。如:

CActiveScheduler::Install(CAtv)
CActiveScheduler* sco2 =CActiveScheduler::Current();

Add()方法可以把活动对象添加到活动规划器中,这样活动规划器就可以处理活动对象所提出的异步请求,当异步请求完成事件发生时调用活动对象的RunL()方法。
活动规划器的 Add() 方法把指定的活动对象添加到当前的活动规划器中。但是在某些时候可能需要删除在活动规划器中的活动对象:如果活动对象被销毁,那么同时活动对象就在活动规划器中被删除了,另外使用活动规划器的Deque()方法也可以把活动对象从活动规划器中删除。
在调用Add()方法之前必须确认活动规划器已经安装到当前线程,否则将发生E32USER-CBase 44 panic。Add()方法包括一个CActive型的参数, 这是一个指向活动对象的指针,在调用Add()方法时,这个指针不可以为 NULL,否则函数发生 E32USER-CBase 48 panic。另外与要注意的是,在使用Add()方法之前确认活动对象是否已经添加到活动规划器中,如果这个对象已经添加到活动规划器中那么将发生E32USER-CBase 41 panic。
CActiveScheduler::Start()方法启动一个在当前活动规划器控制下的的循环等待。而CActiveScheduler::Stop()则可以终止活动规划器的循环等待。活动规划器实际上是构建了一个事件处理的框架,它在循环中使用User::WaitForAnyRequest()等待活动对象的异步请求,当请求事件完成时,就调用一该请求事件相关的活动对象的RunL()方法。活动对象调用RunL()时把它放在TRAP下,这样即使RunL()函数发生Leave活动对象也有可以对它处理,执行一些必要的内存清理。当RunL()发生异常退出之后,首先进入活动对象的RunError()方法,这个方法是一个需方法,有一个默认实现,并返回一个整型的值,如果返回的整形值不是KEeeNone,那么活动规划器将调用自己的Error()方法。Error()方法也有一个默认的实现:发生一个E32USER-CBase 47 panic。

活动对象(Active Object)

CActive类是活动对象(Active Object)的核心类,所有的活动对象类应该从这个类继承。在CActive 中,封装了两个功能:一个是向异步服务提供者发出异步请求;另外一个是处理请完成事件。一个应用程序中可以包含若干个活动对象,并且是它们的都在活动规划器(active scheduler)的控制下运行。
活动对象应该包括一个和请求函数相关连的请求对象,请求函数是专门用于完成异步请求的,由于带有请求函数的对象提供了异步服务,所以在SDK的文档里又把他们叫做异步服务提供者(asynchronous service provider)。这里可以举一个简单的例子,RTimer中包括了一些请求函数,如After()、At()等。所以RTimer型的对象是一个异步服务提供者。所以在活动对象中,通常会有一个服务提供者成员,它的作用是提出一个异步请求,并异步完成他们的请求。
活动对象的 RunL() 方法用于处理活动对象的请求完成事件。活动对象类作为CActive类的派生类,它必须实现RunL()方法。如果有业务逻辑需要,RunL()可以发出另外一个新的异步请求。当请求结束事件发生时,这个函数被活动规划器调用,也就是说,在活动规划器的WaitForAnyRequest()函数结束以后RunL()函数被调用。WaitForAnyRequest()实际上是对User::WaitForAnyRequest()的调用。
在调用活动对象的RunL方法前,活动规划器需要确定,哪一个活动对象拥有最高优先权,然后标记该对象的请求作为请求完成事件。这个优先权是在活动对象构造的时候指定的。RunL运行在活动规划器所设定的TRAP中,如果RunL在执行过程中发生leave那么,那么活动规划器将调用RunError()方法。一旦调用了活动规划器的 Start方法,那么所有的用户代码将运行在活动规划器的某个活动对象的RunL()函数或RunError函数下。
活动对象 RunError() 方法用于处理请求结完成事件处理函数RunL() 的异常退出(Leave)。如果活动对象的RunL()函数发生Leave,那么活动规划器将调用活动对象的RunError方法。这样这个发生错误的活动对象就可以完成一些必要的内存清理。如果有派生类实现了这个方法,那么就应该对可能的 leave 做出处理,并返回KErrNone;如果返回了其他的错误码,那么CActiveScheduler类的Error()将被调用。
RunError() 方法的默认实现返回一个异常退出码,而一旦发生leave这个异常退出码是一个非KErrNone的值,这样可以认为:一般情况下,活动对象的RunL()方法如果发生leave,那么CActiveScheduler类的Error()将被调用。
注意:如果活动对象用于错误处理,那么在他的派生类中应该提供一个比较好的CActiveScheduler Error函数,以便处理那些错误。
活动对象的SetActive()方法用于向活动规划器提交一个异步请求。派生类应该在请求发出以后调用这个方法。活动对象也可以通过一定的方法来取消请求,但是这里需要说明的是,取消请求只是让请求提前完成。关于异步请求取消可以使用Cancel()方法。

请求状态(TRequestStatus)

服务提供者为了完成服务,需要有一个如何完成服务的信息。TRequestStatus 类的对象用于存放为服务提供者定制的请求完成状态。当线程提出请求时,例如RTimer 的After()请求,请求状态对象被作为参数传递到请求函数中。服务完成以后,服务提供者发送信号给服务请求对象的请求信号量并在请求对象中保存一个完成码,通常这个完成码的值是KErrNone或者是其他系统定义的错误码。TRequestStatus 类是密封类,不再被继承。

活动规划器和活动对象协同工作

这里的代码和附件中的例子一致,可以下载例子直接在VC6++中调试。活动规划器和活动对象的的协同工作过程如下。首先应该创建一个与当前线程相关的活动规划器:

CATV* sco = new(ELeave) CATV();
// 推入清理栈
CleanupStack::PushL(sco);
// 把 sco 活动规划器安装为当前线程的活动对象
// 安装以后,sco 活动对象专门处理当前线程的请求事件
CActiveScheduler::Install(sco);

接着创建一个活动对象,TmCount类是一个从CActive类派生的活动对象类。

TmCount* timeCount = TmCount::NewLC();

然后可以从NewLC()方法中看到活动对象构造的具体细节:

TmCount* TmCount::NewLC()
{
console->Printf(_L("NewLC\n"));
// 使用 C++默认构造函数构造
TmCount* result = new (ELeave) TmCount();
// 把活动对象推入清理粘
CleanupStack::PushL( result );
// 调用两阶段构造函数,构造活动对象内部成员
result->ConstructL();
// 返回活动对象指针
return result;
};

在这个默认的构造函数中,还调用了默认的C++构造函数,这个比较关键,因为正是在这个构造函数中,把活动对象添加到了活动对象,在这个;函数中,还可以定义活动对象的优先权限:

TmCount::TmCount():CActive(0) // 这里可以设置活动对象的优先级
{
// 把自己加入活动规划器
iTimeCounter=1;
CActiveScheduler::Add(this);

};

在ConstructL()中,则获得了一个记时器句柄
void TmCount::ConstructL()
{
// 初始化计数器和定时器
iTimeCounter = 0;
User::LeaveIfError(iTimer.CreateLocal());
console->Printf(_L("ConstructL\n"));
};

这样,活动对象构造完成。接着,程序调用活动对象的请求函数。

timeCount->StartL(1);

在活动对象的请求函数中:

// 活动对象的请求函数
void TmCount::StartL(TInt mT)
{
// 将请求传递给异步服务提供者,
// 异步服务提供者,调用请求函数,
// 这里需要注意iStatus 这个参数,它是一个请求状态对象。
// 设定定时器状态为每隔mTime秒钟状态完成一次
iTimer.After(iStatus, 10000 * 100 * mT);
// 在调用异步请求函数之后调用SteActive()方法,提交异步请求
SetActive();
};

异步服务提供者处理请求,当请求处理完成以后,它生成一个事件。活动规划器不断的检查是否有请求完成(是否有请求完成事件发生),如果有请求完成,那么调用与该请求相关联的活动对象的RunL()方法:

void TmCountTwo::RunL()
{
// 计数器+1以后继续提交延时请求事件
console->Printf(_L("[2]The Count is ->>%d\n"), iTimeCounter++);
// 再次提出异步请求
StartL(1);
};

在这个方法中又提出了一次异步请求,并使计数器值增加。如果条件符合则可以不再让活动对象提出异步请求,这是一个修改后的RunL():

void TmCount::RunL()
{
// 计数器+1以后继续提交延时请求事件
console->Printf(_L("[1]The Count is ->>%d\n"), iTimeCounter++);
// 当记数器值大于等于10时,不再提出异步请求。
if (iTimeCounter<10)
{
StartL(1);
}
else
{
//取消请求
Cancel();
}
//User::Leave(9);
};

如果RunL()在运行过程中发生错误,则RunError()被调用,以下是RunEror()方法:

TInt TmCount::RunError(TInt aError)
{
console->Printf(_L("[1]Error\n"));
//console->Getch();
return 7;
};

如果RunError()方法返回了一个非KErrNone的值那么活动规划器的Error方法将被调用。
class CATV :public CActiveScheduler
{
public :
IMPORT_C void Error(TInt anError) const;

};

void CATV::Error(TInt anError) const
{
console->Printf(_L("Error:%d\n"),anError);
//CATV::Stop();
};

posted on 2007-10-12 18:43 郭天文 阅读(847) 评论(0)  编辑 收藏 引用 所属分类: S60


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理