Symbian 活动对象彻底理解

 

Symbian OS中的活动对象的使用无疑是最基础的、最频繁的、最重要的。什么是活动对象呢?

大家学习一个新的事物时,总是会将这个新的事物与自己认知的事物相比较,从而达到快速学习的目的。我开始学习Symbian的时候,我查看很多 Symbian书籍、网上很多Symbian教程都将活动对象与多线程联系到一起,我也总是会把活动对象想象成一个线程。然而,经过了更深入的接触,我发现并不像我想象的那样。

现在,我在此向你保证:活动对象和多线程没有任何关系!不要拿平时做多线程的思想去理解活动对象!

活动对象可以按照以下步骤这么理解:

  1. Symbian OS中提供了很多异步函数,这些异步函数大多部分都是基于“服务器-客户端”架构的。这里与win32 api中的函数有很大的不同。如:
    win32中,CSocket::Receive(是recv而不是WSARecv)为同步函数,线程阻塞在Receive处,直到套接字接收到了网络流才返回
    Symbian OS也有类似的函数,RSocket::Receive,但是此函数是一个异步函数,线程不会阻塞在Receive处而会继续执行
  2. 如何区分Symbian中的函数哪些是同步的哪些是异步的?很简单:看函数内是否包含类型为TRequestStatus的形参,如果有则函数为异步函数。如RSocket::Receive的函数原型为:
    IMPORT_C void Recv(TDes8 &aDesc, TUint flags, TRequestStatus &aStatus);
  3. 参数aStatus为一个状态位,初始值为ERequestPending(值为1),它意味着用户请求的操作是否执行完毕。如:我们调用异步函数RSocket::Receive请求接收网络流,Receive函数会直接返回。当“接收”网络流的过程完毕后,aStatus会变为 EActive,所以我们只要监视aStatus是否不为ERequestPending就可以知道“接收”是否完成了。
  4. 我们可以使用以下伪代码完成以上所述操作:
    TRequestStatus status(KRequestPending);
    RSocket::Receive(aDesc, flags, status);
    for (;;)
    	{
    	if (status != KRequestPending) break;
    	}
    // 此处我们已经通过RSocket::Receive完成了类似CSocket::Receive的同步的工作
  5. Symbian OS不建议我们使用以上方法,它建议我们使用异步方法,而不要使用我们这种方法去强制同步,活动对象就是帮我们做这件事情。活动对象体系帮我们监视aStatus的值,只要aStatus != ERequestPending,他就会以事件的方式通知我们,告诉我们“Socket已经接收完毕,你可以去取数据了!”,活动对象就是干这事的。
  6. 总结一下:系统中有一个“活动调度器”,我们建立一个“活动对象ao1”,将该对象与某个系统中的异步函数绑定,然后将该活动对象注册到“活动调度器”中,“活动调度器”会等待异步函数返回的“完成”消息。收到完成消息后,调度器遍历所注册的活动对象,如果发现status != KRequestPending则找到该status对应的“活动对象ao1”,调用其中的RunL方法,以事件的方式告知我们异步函数已经执行完成。

Symbian编程总结-基础篇-活动对象正解(2)-使用活动对象

我们已经大致了解了活动对象的基本概念,要使用活动对象机制,需要用到活动对象、活动调度器、异步函数。我们想使用异步函数,要按照应用程序->活动对象->活动调度器->异步函数的流程来使用。接下来我们开始进入实战,使用活动对象。

一、创建活动调度器

我们知道,活动调度器是应用程序和异步函数之间的桥梁,应用程序使用活动对象通过活动调度器去截获异步函数的返回“完成”消息,并以事件的方式通知应用程序。

使用Carbide C++ 1.3,通过模板向导生成的控制台程序自动为我们生成了创建活动调度器的代码:

  
  
CActiveScheduler * scheduler = new (ELeave) CActiveScheduler(); CActiveScheduler::Install(scheduler);

CActiveScheduler::Install()方法调用以后,内部代码就会将scheduler指针赋值给CActiveScheduler类内部的静态指针,后面的代码就可以方便的使用CActiveScheduler类的静态方法,如:

  
  
IMPORT_C static void Add(CActive * aActive); IMPORT_C static void Start(); IMPORT_C static void Stop();
  • Add()方法:将活动对象加入活动调度器中注册,以备使用
  • Start()方法:启动活动调度器,活动调度器将开始循环等待异步函数返回的通知消息
  • Stop()方法:停止活动调度器

二、创建活动对象

1、我们创建的活动对象必须派生自CActive类,CActive类已经为我们准备好了iStatus成员变量:

  
  
public : TRequestStatus iStatus; private : TBool iActive;

另外一个成员变量iActive起着标识作用,证明该活动对象已经请求了异步函数,如:

  
  
RTimer::After(iStatus, 1000000 ); SetActive();

SetActive()方法为基类CActive的方法,其实就是将iActive = ETrue;,用来标识活动对象已经调用了异步函数。所以,我们只要调用了异步函数,在调用异步函数的代码后面应该紧挨着调用SetActive()方法的代码。

2、有两个虚方法必须继承:

  
  
virtual void DoCancel() = 0 ; virtual void RunL() = 0 ;
  • RunL方法:活动调度器接收到异步函数返回的“完成”消息后,遍历在其注册的所有活动对象,如果活动对象的iActive = ETrue且iStatus != KRequestPending则调用活动对象的RunL方法,并将iActive设置成EFalse,以防下次轮询时仍然调用此活动对象。
    在这里“RunL”这个名字会让很多人产生歧义,我刚开始接触的时候总以为和J2me中的Runnable接口的run方法差不多。其实在这里把“RunL”改为“NotifyRequestCompleteL”更贴切些。

    再次声明一下,调用异步函数时,参数TRequestStatus& status都是以引用的方式传递的,如:
        
        
    IMPORT_C void After(TRequestStatus & aStatus, TTimeIntervalMicroSeconds32 anInterval);

    所以异步函数内部可以改变status的实参,也就是改变活动对象的类成员iStatus。
  • DoCancel()方法:基类CActive中有取消异步函数的方法Cancel(),调用Cancel()后,活动对象会通过DoCancel()方法通知应用程序做取消方法的后期工作,如删除对象及回收指针等。注意:在应用程序中如果想终止活动对象,要使用Cancel()方法调用而不是DoCancel()方法。

3、活动对象的带优先级的构造函数:

基类CActive的构造函数原型如下:

  
  
protected : IMPORT_C CActive(TInt aPriority);

此处将传入一个优先级枚举值,枚举值内容如下:

  
  
/* * Defines standard priorities for active objects. */ enum TPriority { /* * A low priority, useful for active objects representing background processing. */ EPriorityIdle =- 100 , /* * A priority higher than EPriorityIdle but lower than EPriorityStandard. */ EPriorityLow =- 20 , /* * Most active objects will have this priority. */ EPriorityStandard = 0 , /* * A priority higher than EPriorityStandard; useful for active objects handling user input. */ EPriorityUserInput = 10 , /* * A priority higher than EPriorityUserInput. */ EPriorityHigh = 20 , };

当调用CActiveScheduler::Add方法注册活动对象时,活动调度器会按照活动对象的优先级进行排序,插入或添加到活动对象集合中。在此会起到如下作用:当多个异步函数消息同时返回时(多个iStatus同时不为KRequestPending),活动对象调度器轮训集合的时候总是会先找到优先级高的活动对象并调用其RunL方法。

但是在通常情况下,我们会在构造函数传入EPriorityStandard。

三、活动调度器Start方法的伪代码

通过以上两点分析,我们完全可以模拟出CActiveScheduler::Start方法:

  
  
void CActiveScheduler::Start() { for (;;) { // 挂起线程直到异步函数消息返回 // 注:活动调度器和应用程序不在一个线程,所以应用程序不会阻塞 User::WaitForAnyRequest(); // 如果异步函数和主程序在不同的线程则RThread::WaitForAnyRequest(); // 当消息返回的时候线程会苏醒 // 以优先级降序的方式检测调度器集合中每个活动对象 for (;;) { // 调用第一个已完成且iActive == ETrue的活动对象事件处理函数 if (activeObject -> IsActive() && activeObject -> iStatus != KRequestPending) { // 找到一个已准备好处理事件的活动对象 // 重置iActive状态以表明其不再是活动状态了 activeObject -> iActive = EFalse; // 在TRAP中调用活动对象的事件处理函数 TRAPD(err, activeObject -> RunL()) ; if (err != KErrNone) { // 如果异常则调用活动对象的RunError方法 err = activeObject -> RunError(); if (err != KErrNone) { Error(err); } } break ; } } } }

 

四、使用活动对象的例子

点击此处下载源代码

此例子将启动一个控制台程序,并使用异步服务类RTimer定时器,每隔一秒在屏幕上显示累加的数字,效果如下:

image

 

五、小结

在这一节中,我们基本了解了活动调度器、活动对象的工作机制及工作流程,在下一节里,我们将深入活动对象的内部,了解其工作原理,进一步加深对活动对象的理解。

 

Symbian编程总结-基础篇-活动对象正解(3)-活动对象的工作原理

我们已经知道如何创建和使用活动对象,大家对活动对象的创建、使用都有了一定的了解。在这一节里我将深入活动对象机制,分为“活动对象的工作流程”、“信号迷失错误”两个部分,为大家剖析活动对象的工作原理。

一、活动对象工作流程

首先我们用时序图来说明一下应用程序、活动对象、活动调度器及异步函数服务器之间创建及调用的流程:

image

下面我们针对每一个步骤结合代码(点击此处下载代码)进行说明:

1、创建并安装活动调度器:

  
  
CActiveScheduler * scheduler = new (ELeave) CActiveScheduler(); CleanupStack::PushL(scheduler); CActiveScheduler::Install(scheduler);

如果创建了一个基于GUI应用程序框架的应用程序,框架已经为我们创建并且安装了活动调度器,我们可以直接使用CActiveScheduler的一系列方法。

2、创建活动对象

  
  
iMyAO = CMyActiveObject::NewL( * console);

此处创建的CMyActiveObject类是继承自CActive类的活动对象。

3、将活动对象添加到活动调度器中

  
  
void CMyActiveObject::ConstructL() { .... CActiveScheduler::Add( this ); // Add to scheduler }

可以看到,活动对象在通过“二阶段构造”创建时就已经将自己的指针添加到了活动调度器中。

4、StartL

StartL为应用程序请求活动对象调用异步函数的方法,此处用户可以根据自身需求对此方法重新命名:

  
  
void CMyActiveObject::StartL(TTimeIntervalMicroSeconds32 aDelay) { Cancel(); // 取消异步函数请求 iStatus = KRequestPending; iTimer.After(iStatus, aDelay); // 在此处调用异步函数 SetActive(); // 将成员变量iActive = ETrue }

因为不能保证用户在等待异步函数调用完毕返回的时候不重新调用StartL方法,所以在StartL方法的入口点首先调用Cancel()方法取消异步请求,否则可能会发生臭名远扬的“信号迷失”错误。

5、iStatus = KRequestPending

在以上代码StartL方法中,异步函数调用之前,首先要将iStatus设置为KRequestPending以便活动调度器遍历时匹配。

6、请求异步函数并发送iStatus

在StartL方法中,iTimer.After(iStatus, aDelay);这行代码传递了成员变量的引用并调用了异步函数iTimer.After。

7、SetActive

调用基类CActive的SetActive方法,方法内部将iActive设置成ETrue,以便活动调度器遍历时匹配。

8、启动活动调度器

  
  
CActiveScheduler::Start();

10、查找对应的活动对象

我们在上一节已经通过分析并还原了CActiveScheduler::Start()方法的伪代码,此代码块在另外一个线程遍历所有向调度器注册的活动对象,查看对象的iStatus不为KRequestPending且iActive为ETrue。因为异步函数服务器完成了请求的工作以后,会改变iStatus的实参,使其不等于KRequestPending,再加上活动对象在调用完异步函数返回后马上改变了iActive值为ETrue,所以活动调度器只要判断iStatus != KRequestPending && iActive == ETrue则可以知道哪一个活动对象所请求的异步服务已经完成。

9、WaitForAnyRequest()等待异步函数返回通知,11、RequestComplete()并将iStatus的值改变

当异步服务已经完成了所请求的工作,它会使用User::RequestComplete()发送一个通知,活动调度器会通过 User::WaitForAnyRequest()或RThread::WaitForAnyRequest()收到这个通知,随后遍历向其注册的条件匹配的活动对象(第8点下划线部分说明)。

12、调用RunL方法

如果找到了异步函数所对应的活动对象,则调用活动对象的RunL方法,RunL方法在TRAP宏里运行。如果RunL方法抛出异常,活动调度器会自动调用活动对象的RunError方法。因此大多数情况下不需要在RunL内编写异常捕获的代码。

活动对象调用完RunL方法后,将活动对象的iActive值设置为EFalse,以便在下次遍历集合时跳过已经处理过的活动对象。

如果没有找到异步函数所对应的活动对象,活动调度器将抛出一个“信号迷失”异常。

13、CActiveScheduler::Stop()

停止活动调度器,停止监控异步函数返回的信号。

二、活动对象专属错误-“信号迷失”

从上面的分析我们知道,活动调度器会拦截异步函数返回的消息,随后遍历活动对象集合找到相应的活动对象调用,如果没有找到则抛出“信号迷失”异常。有以下两种方法会导致“找不到活动对象”的发生:

  1. 活动对象根本没有注册到活动调度器中:没有调用CActiveScheduler::Add方法注册活动对象
  2. 不满足活动调度器查找活动对象的标准iStatus != KRequestPending && iActive == ETrue:在调用异步函数后,没有调用SetActive()方法将iActive的值设为ETrue;或者将iStatus同时传给两个异步函数,导致第二个异步函数返回时,当前活动对象的iActive为EFalse,调度器找不到相应的活动对象。

三、小结

在这一节里,我们学习了活动对象的内部工作原理,让我们对活动对象处理机制有了更深入的了解。在下一节里,我们将学习如何同步调用现有的异步函数。

 

Symbian编程总结-基础篇-活动对象正解(4)-异步函数的同步调用

我们深入了解了活动对象、活动调度器及异步函数服务器的工作原理及运行机制,想必大家对活动对象的机制及体系结构的运用已经了如指掌。但是大家有没有觉得异步函数使用起来比较麻烦呢?难道非要使用活动对象,非得以"异步"的方式调用异步函数吗?这一节将为大家解决这个问题:异步函数的同步使用。

 

一、使用CActiveSchedulerWait类

        在以前的文章"Symbian编程总结-界面篇-打开jpeg/gif/png图像"里我们已经看到了CActiveSchedulerWait类的使用方法,在此我再详细介绍一下。

        很多初学者在开始时会将CActiveScheduler和CActiveSchedulerWait类弄混,CActiveScheduler是活动对象的调度器,而CActiveSchedulerWait可以简单的理解为一个当前线程的阻塞器

  • 调用CActiveSchedulerWait::Start()方法时,线程阻塞;
  • 调用CActiveSchedulerWait::AsyncStop()方法时,请求停止对线程的阻塞

        因此,我们在不修改原来活动对象代码的情况下,只要简单的在异步函数调用方法后加上"CActiveSchedulerWait::Start()",在活动对象的RunL方法的开头加入"CActiveSchedulerWait::AnsycStop()"就可以了。

        针对上一节教程介绍的控制台应用程序,我们对以下几个方法(下划线为修改部分)进行修改:

 

点击此处下载源代码

 

CActiveSchedulerWait* iWait;

 

void CMyActiveObject::ConstructL()

    {

    User::LeaveIfError(iTimer.CreateLocal() ); // Initialize timer

    CActiveScheduler::Add( this); // Add to scheduler

 

    iWait = new (ELeave)CActiveSchedulerWait;

    }

 

CMyActiveObject::~CMyActiveObject()

    {

    Cancel(); // Cancel any request, if outstanding

    iTimer.Close(); // Destroy the RTimer object

    // Delete instance variables if any

 

    if (iWait->IsStarted())

        {

        iWait->AsyncStop();

        }

 

    delete iWait;

    iWait = NULL;

    }

 

void CMyActiveObject::StartL(TTimeIntervalMicroSeconds32 aDelay)

    {

    Cancel(); // Cancel any request, just to be sure

 

    iTimer.After(iStatus, aDelay); // Set for later

    SetActive(); // Tell scheduler a request is active

 

    iWait->Start(); // 1

    }

 

void CMyActiveObject::RunL()

    {

    iWait->AsyncStop(); // 2

 

    TBuf<50> outputStr;

    outputStr.AppendNum(iCount);

    iConsole.Write(outputStr);

    iConsole.Write(_L("/n"));

    iCount++;

    }

 

    使用CActiveSchedulerWait的几点注意事项:

  1. CActiveSchedulerWait必须结合活动对象使用,而且使用方法只有以上代码介绍的那一种;
  2. Start方法和AsyncStop方法必须成对出现;
  3. 程序退出时要检查CActiveSchedulerWait是否在IsStarted()状态,如果是则调用AsyncStop方法。否则程序不能正常退出;
  4. CActiveScheduler类内部有自己的静态指针,提供的静态方法都调用了内部的静态指针。而CActiveSchedulerWait 类没有内部静态指针,方法也不是静态的,我们必须自己管理CActiveSchedulerWait类的全局指针,在这点上程序要经过良好的设计。

 

二、使用User::WaitForRequest方法

    如果不想使用活动对象,也不想使用难于管理的CactiveSchedulerWait,你可以使用User::WaitForRequest方法。以下是User::WaitForRequest方法的原型:

IMPORT_C static void WaitForRequest(TRequestStatus& aStatus);

        此方法将等待异步函数服务器返回的信号量,然后匹配aStatus参数。如果接收到的信号与参数aStatus一一匹配,则跳过阻塞进入下一行代码,否则继续阻塞线程直到aStatus对应的信号通知返回。

User::WaitForRequest还有一个重载的方法,它可以监视两个信号的通知:

IMPORT_C static void WaitForRequest(TRequestStatus& aStatus1,TRequestStatus& aStatus2);

         有了User::WaitForRequest,异步函数使用起来就变得非常方便,我们不需要创建活动对象,也不需要创建成员变量 TRequestStatus,只需要声明局部的TRequestStatus、局部的异步函数类,在异步函数调用之后,加入 User::WaitForRequest(status),就能够使线程阻塞在User::WaitForRequest处直到status对应的异步函数处理完成。

LOCAL_C void DoTestL()

    {

    RTimer timer;

    CleanupClosePushL(timer);

    User::LeaveIfError(timer.CreateLocal());

   

    TRequestStatus status(KRequestPending);

   

    // 调用异步方法并将status传入

    timer.After(status, 1000000);

   

    // 等待status对应的信号量,此处用了User::WaitForRequest方法将异步方法的调用模拟成同步

    User::WaitForRequest(status);

    CleanupStack::Pop(&timer);

    }

 

LOCAL_C void MainL()

    {

    TInt n = 0;

    TBuf<10> str;

    _LIT(KNewLine, "/n");

   

    FOREVER

        {

        DoTestL();

        str.Num(n);

        n++;

       

        console->Write(str);

        console->Write(KNewLine);

        }

    }

 

三、使用User::WaitForRequest方法的问题

        User::WaitForRequest有时候不会正常运行,如:CImageDecoder::Convert方法:

IMPORT_C virtual void Convert(TRequestStatus* aRequestStatus, CFbsBitmap& aDestination, CFbsBitmap& aDestinationMask, TInt aFrameNumber = 0);

        最后一个参数必须为EOptionAlwaysThread,User::WaitForRequest才能正常执行,个人认为 CImageDecoder::Convert如果没有加EOptionAlwaysThread参数的时候是使用"长线任务"(将在下一节介绍)实现的。所以,CImageDecoder::Convert方法应该按照如下方法调用:

    TRequestStatus status(KRequestPending);

    CImageDecoder* decoder = CImageDecoder::FileNewL(iFs, aFileName, KMIMEType,, CImageDecoder::EOptionAlwaysThread);

   

    decoder->Convert(&status, aBitmap);

    User::WaitForRequest(status);

   

    delete decoder;

    decoder = NULL;

 

四、小结

        本文介绍了在如果将异步函数同步使用。其实在Symbian的早期编程中还有一种异步函数的同步方法,那就是在异步函数调用后使用 CActiveScheduler::Start()方法嵌套活动调度器。但是这种方法已经被Symbian 7.0后的CActiveSchedulerWait替代,在此处就不再介绍。

        另:在介绍活动对象的专题里涉及到很多Symbian OS的客户/服务器架构的知识,我会在近期另外开一个专题讲解,请大家耐心等待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值