http://www.developer.com/ws/brew/article.php/1497121/Cooperative-Multitasking-in-BREW-A-Possible-Solution.htm
BREW is an event-based execution environment with no support for multithreading and where long tasks are specifically discouraged. BREW offers native mechanisms to overcome the problems usually associated with the lack of these features, but implementing and maintaining them in non-trivial code is complicated. This article introduces a possible implementation of cooperative multitasking in the BREW environment. This is actually a framework that allows fast and easy development of concurrent tasks. The debut of the article reviews the main functionality offered by BREW in this respect with a presentation of the framework following. A link is included at the end of the article for you to download the full source code. BREW can handle one thread of execution and one applet at a time. This means that running a long task might block access to the event loop and result in a non-responsive user interface (UI). Because of this, most devices running a RTOS have a watchdog that checks the threads periodically. On BREW enabled Qualcomm devices (running REX RTOS), if an application takes too much of the CPU and does not yield so that another task on the phone can be executed, the phone will be reset. BREW provides a callback mechanism used to segment a long task in several small ones to prevent this reset. Control can be relinquished between these tiny execution sequences to the event loop. To implement this functionality a callback is registered with the shell, like: Step 1. Filling the AEECalback structure cb: There is a helper function that might be used for this purpose: Step 2: Invoke ISHELL_Resume. This will pass control to the callback function when the event loop is called next time, adding the callback to a list of pending operations. If the callback has already been registered, it is cancelled (deregistered) and then re-registered. Additional functionality: If a callback has already been registered but not executed, it can be cancelled by: or using the helper function: Using this functionality allows cooperative multitasking, like: Several tasks can execute small pieces of their entire job and yield to each other, synchronizing their work or not. As the above example demonstrates, Tasks implementation involves a fairly cumbersome and error prone infrastructure to be handled — creating tasks, deleting tasks, keeping track of them, the actual implementation, etc. This is without even considering adding timer and/or notify/wait support. A simple framework might help in this respect. In fact, considering that threading may lead to poor performance due to context switching, synchronization, and data movement a callback based solution might be attractive even on multithreaded platforms. The framework presented here is a simplified version - for a full fledged version please contact Epicad The main actors of this implementation are: Interactions: We'll look into each one of these activities and see how they are reflected in the current framework. Every task implements ITask: where EXEC_STATUS is a simple enumeration of 2 states: A possible trivial Task might look like: Writer is a BREW dependent object that offers printing services and pos_ is the state variable. What does POS stand for? POS stands for Position. Implementing execute() is straightforward. The task is segmented in slices governed by pos_ and the task is run as long as pos_ doesn't exceed a certain value. Create the Dispatcher in the global InitAppData and pass it the IShell reference, used everywhere for BREW specific duties. Stop and Start would normally be implemented in EVT_KEY event. Interrupts all tasks associated with the dispatcher_ if a key event is received (a key was hit) Deleted in the global FreeAppData. Please note that in order to maintain the simplicity of this example, cbkHolder_ was implemented rigidly as an array rather than as a more flexible container. As mentioned earlier operations like start/stop are general level operations, affecting all the tasks and are based on startTask/stopTask. For example: where loop iterates using functions of type: Registering a task needs some precautions: nextAvailablePosition is an internal counter keeping the next slot available for a new Callback. It cannot be incremented as long as addTask fails. addTask might fail if: In all these cases the task is deleted and a BREW error code is returned. The most interesting part is obviously the Callback implementation: Internally a Callback maintains its position in the callback stack as well as references to the associated Task and to the Dispatcher. A Callback wraps the BREW callback mechanism — meaning that this is the only class needing changes when porting on a different platform. executeStatic() is the actual workhorse, the method registered in the callback mechanism. It's centered on the 2 possible states transmitted by the Task: This code was tested using BREW2.0 SDK version 2.0.0.32. Tests conducted on the emulator on previous SDK releases (including 1.1) revealed an abnormal behavior when sending event keys — these events were queued until the end of the task(s) — meaning that actually there is no way to interrupt task(s). There are other mechanisms in BREW that might be used instead of callbacks — notably timers and PostEvent. Unfortunately there are other issues that might deter their use. Most devices enter into a sleep mode when there is no keypad activity on the handset for a fixed duration. When in this mode timers expire much more slowly than the actual duration set for the timer. On BREW 2.0 one can use EVT_APP_NO_SLEEP event (returning TRUE from this event changes the above described default behavior). The problem is that this event exists only on BREW 2.0 and its implementation is at the discretion of the OEM. PostEvent behaves exactly like callbacks on previous versions - it's a known bug Qualcomm is aware of. Advantages of using the framework: Downloads: Source Code - 37 kb. Radu Braniste is Director of Technology at Epicad. He can be contacted at rbraniste@epicad.com # # #
Cooperative Multitasking in BREW: A Possible Solution
November 8, 2002
Abstract:
BREW "Native" Ways
cb.pfnCancel = (void *)NULL; //updated by shell
cb.pfnNotify = ResumeNotifyCB; // address of the
// callback function
cb.pNotifyData = (void *)pMe; //data to pass
void CALLBACK_Init( AEECallback * pcb,
PFNNOTIFY pfn,
void * pd);
ISHELL_Resume (pMe->a.m_pIShell, &cb);
if(cb.pfnCancel) cb.pfnCancel(&cb);
void CALLBACK_Cancel(AEECallback * pcb);
static void Task0 (void *pi)
{
CIShellApp *pMe = (CIShellApp *)pi;
pMe->callBack0_.pfnCancel = (void *)NULL;
pMe->callBack0_.pfnNotify = Task0;
pMe->callBack0_.pNotifyData = (void *)pMe;
executeTask0JobAtStep (pMe->step_); //task related functionality
pMe->step_++;
ISHELL_Resume (pMe->a.m_pIShell, &pMe->callBack_);
}
//other tasks executeTaskNJobAtStep (pMe->step_);
A simple framework saves the day.
a. Developer implements a set of tasks.
class ITask
{
public:
virtual EXEC_STATUS execute() = 0;
virtual ~ITask(){};
};
const static enum EXEC_STATUS {CONTINUE, STOP};
AsyncTask2::AsyncTask2( IShell* shell, int lineNo) :
pos_(0), lineNo_(lineNo)
{
writer_ = new Writer(shell);
}
EXEC_STATUS AsyncTask2::execute()
{
pos_ += 10;
if (pos_ < 30001)
{
doStepJob();
writer_->writeIntAtLine(pos_, lineNo_);
// signal intention to continue activity
// requests to be registered for a new task slice
return CONTINUE;
}
// requests termination of current task
return STOP;
}
void doStepJob()
{
//do real stuff here.
}
AsyncTask2::~AsyncTask2()
{
delete writer_;
};
b. A Dispatcher is created:
dispatcher_ = new Dispatcher(m_pIShell);
c.Tasks are registered with teh Dispatcher
dispatcher_->registerTask(new AsyncTask1(m_pIShell)) ;
//other tasks registered.
d. Dispatcher is instructed to start the activity of Tasks
dispatcher_->start();
e/f. Task Run and Tasks may be stopped/started...
dispatcher->stop();
g. The Dispatcher is deleted
delete dispatcher_;
Inside the framework
class Dispatcher
{
public:
Dispatcher(IShell* shell);
int registerTask(ITask* task);
void start();
void startTask(int i) ;
void stop();
void stopTask(int caller);
~Dispatcher();
private:
void loop(doCurrentIndexJob job);
void initTask(int i);
static boolean isOutOfBoundaries(int pos);
int addTask(ITask* task, int position);
private:
Dispatcher();
Dispatcher(const Dispatcher&);
Dispatcher& operator=(const Dispatcher&);
private:
IShell* shell_;
CallbackHandle* cbkHolder_[POS_LIMIT];
int nextAvailablePosition_;
};
void Dispatcher::startTask(int i)
{
if (cbkHolder_[i]) cbkHolder_[i]->start();
}
void Dispatcher::start()
{
loop(startTask);
}
typedef void (Dispatcher::*doCurrentIndexJob)(int pos);
int Dispatcher::registerTask(ITask* task)
{
int err = addTask(task, nextAvailablePosition_);
if (err == SUCCESS) ++nextAvailablePosition_;
return err;
}
int Dispatcher::addTask(ITask* task, int position)
{
if (!task)
return EFAILED;
if (
isOutOfBoundaries(position)
||
cbkHolder_[position]
)
{
delete task;
return EFAILED;
}
cbkHolder_[position] =
new CallbackHandle(shell_, task, this, position);
if (!cbkHolder_[position] ) return EFAILED;
return SUCCESS;
}
class CallbackHandle
{
public:
CallbackHandle( IShell* shell, ITask* task,
Dispatcher* d, int position) :
shell_( shell), task_(task), dispatcher_(d),
position_(position)
{
}
virtual ~CallbackHandle()
{
release();
delete task_;
task_ = 0;
}
void start()
{
CALLBACK_Init(&cbk_, executeStatic, this);
executeResume(this);
}
private:
static void executeStatic(CallbackHandle* cb)
{
ITask* t = cb->task_;
if (!t) return;
Dispatcher* d = cb->dispatcher_;
switch (t->execute())
{
case CONTINUE:
executeResume(cb);
return;
case STOP:
d->stopTask(cb->position_);
return;
default:
d->stopTask(cb->position_);
return;
}
}
static void executeResume(CallbackHandle* cb)
{
ISHELL_Resume (cb->shell_, &cb->cbk_);
}
void release()
{
CALLBACK_Cancel(&cbk_);
}
private:
CallbackHandle();
CallbackHandle(const CallbackHandle&);
CallbackHandle& operator=(const CallbackHandle&);
private:
ITask* task_;
IShell* shell_;
AEECallback cbk_;
Dispatcher* dispatcher_;
int position_;
};
void Dispatcher::stopTask(int caller)
{
if (isOutOfBoundaries(caller))
return;
delete cbkHolder_[caller];
initTask(caller);
--nextAvailablePosition_;
}
Implementation observations:
About the Author