活动对象深度理解

来源:互联网 发布:手机淘宝聚划算怎么抢 编辑:程序博客网 时间:2024/04/30 23:01

抢占式多任务操作系统

    Symbian OS中,线程由内核以抢占式进行调度,内核将会执行优先级最高的、可运行的线程。一个线程会为等待某个事件的发生而被挂起,也会在任何适当的时候被重新唤醒。内核控制的线程的调度,它允许线程通过划分时间片的方法共享系统资源,而且也允许每个具有更高优先级的、可执行线程抢占当前运行线程。运行线程不停转换正是抢占式多任务的基础,这使得多个服务器和应用程序能够同时运行。当执行的线程被挂起时,就会发生上下文切换,并且调度器会选择另一个线程使其运行。上下文切换会造成内核调度器方面的运行期开销,如果切换前后的两个线程在不同进程中执行,还会造成内存管理单元和硬件缓存的开销。

对活动对象的理解

     Symbian OS中,活动对象都运行在同一个线程中。尽管活动对象存在于同一个线程,但它们仍然是独立运行的,这就好像同一个进程中的线程都是独立运行的一样。对于所有线程都是由系统内核进行调度,但是活动对象它只是属于线程,由活动对象调度器进行调度。因此在在一个线程内的活动对象是非抢占的协同运行的,但在Symbian OS中,它们所在的线程确实是被抢占式调度的,然而,一旦一个活动对象在处理事件了,它就无法被另一个活动对象的事件处理程序所抢占。

     Symbian OS中用活动对象而非线程的优势所在:
线程间的通信比活动对象间的通信缓慢且困难些(活动对象在同一个线程中运行,因此它们不需要任何特殊方法来共享数据);
线程间的场景切换消耗CPU周期,从而使其性能表现下降。活动对象并不像线程那样会被中断,因此并不需要在各个切换间储存并恢复CPU及内存映射单元(Memory Mapping Unit,MMU)的状态;
许多资源不能在线程间共享。由于活动对象用一个线程运行,它们可以共享资源;
在线程间访问内存或访问其他共享内存通常需要对同步更新提供保护。这由信号量(semaphores)完成,这可能导致代码的复杂化,并造成性能下降。活动对象不用担心同步问题,因为它们是以某种非抢先方式调度的。

     线程间上下文切换和同一线程中活动对象之间传递控制权,在速度上的差别可能达到10倍。另外,一个线程大约在内核中有4KB的空间开销,在用户空间有8KB用于程序栈的空间开销,而一个活动对象的大小可能只有几百个字节,甚至更小。

使用活动对象(一下内容摘自《Symbian OS:活动对象与活动调度器》)

    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()方法中返回。

原创粉丝点击