Support

Symbian Developer Network C++ Code Clinic

August 2008: Active Objects Example Code on the Couch

Code Clinic

Jo Stichbury re-opens the code clinic to dispense more advice about Symbian C++.

In this month's clinic she takes a different approach and shares some early sample code for a long-running active object class that manipulates a dynamic array.

As it's August, and holiday time for many, I thought we'd have a lighter code clinic this month. So I'm simply going to walk through some example code I've created to illustrate a long-running active object that uses a dynamic array. There is lots of code available for both of these concepts already, such as a great paper on this site by Paul Todd on the Advanced Use of RArray, and the Fibonacci number generator example code taken from Symbian OS C++ for Mobile Phones, Volume 3. However, I wanted to create something simple that unites the two, which you can re-use and extend.

An application may need to carry out a task that takes a significant or unpredictable time to complete, such as sorting data or compacting a database. One option is to use a separate thread for the task, but a developer may want to avoid this - for example, because the resulting inter-thread coordination code may be quite complex. The solution on Symbian OS is to break the task into a series of short increments and run them in the main application thread when no higher-priority active object needs to run. This is a commonly-used pattern on Symbian OS, using CActive or CIdle to repeatedly perform a task increment, and then queue another step. I'm going to illustrate a long-running task using CActive.

The bulk of the example is delivered in two separate files (LongRunningTask.h and LongRunningTask.cpp) that you can simply take and re-use if you decide to experiment with the code. I worked with S60, but if you prefer to use UIQ 3, you can easily drop the example code files into a similar basic project.

CBackgroundTask is a low priority active object class. It has a StartTask() method to kick off a long-running task, and a CancelTask() method to cancel it. The application menu provides options to call each of these methods:

class CBackgroundTask : public CActive
  {
public:
  enum TStepState { EWaiting, EAddingLabors, ESortingLabors, ECheckingLabors };
public:
  static CBackgroundTask* NewL(MExampleCallback& aCallback);
  ~CBackgroundTask();
public:
  void StartTask();
  void CancelTask();
protected: // From CActive
  virtual void RunL();
  virtual void DoCancel();
  virtual TInt RunError(TInt aError);
private:
  CBackgroundTask(MExampleCallback& aCallback);
  void SelfComplete();
  void ConstructL();
private:
  void SortLaborsAscending();
  TLabor& GetLabor(TInt aLaborId);
private:	
  MExampleCallback& iCallback; // To notify caller on completion
  TStepState iState;	
  RArray <TLabor>iLaborArray;
  TInt iCounter;
  };

The class doesn't call an asynchronous service provider in another thread, but runs a short activity in the same thread, then uses 'self completion' to generate events. Self completion means that the active object calls User::RequestComplete() on its own iStatus object and then calls SetActive() as shown below:

void CBackgroundTask::StartTask()
  {
  if (IsActive())
    {// Notify the caller that they cannot submit a request yet
    iCallback.TaskComplete(KErrInUse);
    }
  else
    {// Start the task
    iCounter = 0;
    iState = EAddingLabors;
    SelfComplete(); // Self-completion to generate an event
    }
  }

void CBackgroundTask::SelfComplete()
  {// Generates an event on itself by completing on iStatus
  TRequestStatus* status = &iStatus;
  User::RequestComplete(status, KErrNone);
  SetActive();
  }

The active scheduler detects the event signal and calls the active object's RunL() event handler. RunL() inspects the current state of the object and acts according to whether it is EAddingLabors, ESortingLabors or ECheckingLabors. It is, in effect, a lightweight state machine that performs a series of actions in an appropriate sequence. By the way, I've omitted assertion statements and logging from the code below, but you'll find them in the download package.

void CBackgroundTask::RunL()
  {// Perform next increment of the task or stop and callback
  switch (iState)
    {
    case (EAddingLabors):
      {
      TLabor tempLabor(++iCounter);
      iLaborArray.AppendL(tempLabor);
      if (iCounter==KMaxLabors)
        iState = ESortingLabors;
	
      SelfComplete();
      }
      break; 
    case (ESortingLabors):
      {
      SortLaborsAscending();
      iState = ECheckingLabors;
      iCounter = 0;
      SelfComplete();
      }
      break;
    case (ECheckingLabors):
      {
      TLabor labor = GetLabor(++iCounter);
	if (iCounter==KMaxLabors)
        {// We've finished
        iState = EWaiting;

	iLaborArray.Reset();
        iCallback.TaskComplete(KErrNone);
        }
      else
        {
        SelfComplete();
        }
      }
      break;
    default:
      __ASSERT_DEBUG(EFalse, 
      User::Panic(KLongRunningTaskExPanic, EUnexpected));
    }	
  }

The TStepState states illustrate some very basic dynamic array operations, adding, sorting and matching elements of an RArray. The array elements are objects of the TLabor class, which is a basic type that provides two static methods used when sorting and searching the array.

class TLabor
  {
public:
  TLabor(TInt aLaborId);
public:
  static TInt CompareLaborPriorities(const TLabor&, const TLabor&);
  static TBool MatchLabors(const TLabor&, const TLabor&);
public:
  inline TInt Priority() {return (iLaborPriority);};
  inline TInt Id() {return (iLaborId);};
private:
  TLabor(); // Prevent default construction of uninitialized object
private:
  TInt iLaborPriority;
  TInt iLaborId;
  };

// Used by TLinearOrder <class T> in sort operations
// If aLabor1.iLaborId < aLabor2.iLaborId return -ve value
// If aLabor1.iLaborId > aLabor2.iLaborId return +ve value
// If aLabor1.iLaborId == aLabor2.iLaborId return zero
TInt TLabor::CompareLaborPriorities(const TLabor& aLabor1, const TLabor& aLabor2)
  {
  if (aLabor1.iLaborPriority > aLabor2.iLaborPriority)
    return (1);
  else if (aLabor1.iLaborPriority < aLabor2.iLaborPriority)
    return (-1);
  else
    {
    ASSERT(aLabor1.iLaborPriority==aLabor2.iLaborPriority);
    return (0);
    }
  }

// Used by TIdentityRelation <class T> in search operations
// If labors match, return ETrue, otherwise EFalse
TBool TLabor::MatchLabors(const TLabor& aLabor1, const TLabor& aLabor2)
  {
  if (aLabor1.iLaborId==aLabor2.iLaborId)
    return ETrue;
    
  else
  return (EFalse);
  }

These static methods are invoked by the CBackgroundTask object

// Sorts the labors into numerical priority order starting
// from the lowest priority value labor
// Uses RArray::Sort()
void CBackgroundTask::SortLaborsAscending()
  {
  // Creates a TLinearOrder object implicitly
  // from TLabor::CompareLaborPriorities()
  iLaborArray.Sort(TLabor::CompareLaborPriorities);
  }

// Returns a labor from the array with the given labor Id
// Uses RArray::Find()
TLabor& CBackgroundTask::GetLabor(TInt aLaborId)
  {
  TLabor temp(aLaborId);
  
  // Creates a TIdentityRelation object implicitly
  // from TLabor::MatchLabors()
  TInt foundIndex = iLaborArray.Find(temp, TLabor::MatchLabors);
  return (iLaborArray[foundIndex]);
  }

Each step is kept as short as possible, and avoids lengthy or complex processing, so as not to block higher-priority active objects should their event-handlers need to run.

Download the code

You can download all the code from the wiki page we've created here. Please feel free to leave comments on the page to let me know what you think of the code. Do you find it useful provided as a zip file or would you prefer some other way, such as a Carbide.c++ plugin?

Contributions welcome!

It's no great secret that my first book, Symbian OS Explained, is ready for an update. The first edition was written between 2003 and 2004 for Symbian OS v7.0/v7.0s, so a check up is long overdue! I started work on a new edition a while ago, but it's not quite complete because I've been working on our other books. It is slowly taking shape, and the code I've included above will be part of the book - if I receive feedback that it's useful. I'd welcome your advice: Is this kind of example too simplistic? Or is it too complex − would you prefer to see basic skeleton code and pseudo-code instead? Should we put more code in the books, is the balance about right, or are we overdoing it?

I personally don't like to read large chunks of code in books, but prefer to have a full example elsewhere and snippets only in the text. But what do you think? Please let us know by leaving feedback on the wiki page or by e-mailing us at books@symbian.com.

Thank you for any ideas and suggestions you contribute. And if you're looking for some summer reading, may I recommend Quick Recipes on Symbian OS, which was published last month.

Happy holidays!

The Code Clinic will feature regularly on Symbian Developer Network. Got a Symbian C++ question? Send it to the Code Doctor at SDN@symbian.com. Questions used will receive a complementary copy of the Symbian Press book "Developing Software for Symbian OS, Second Edition".

Reviewers

I'd like to thank Hamish Willee, Freddie Gjertsen and Mark Shackman and for their reviews of this month's Clinic.

 
[standby-web] Terms of use | Privacy policy | Media Center | Contact us | © 2008 Symbian