Refactoring Threaded Applications
If moving from synchronous to asynchronous events is hard, then untangling a multi-threaded algorithm is harder. Typically, people write code for multithreaded applications for one of two reasons: either to permit the application to manage user events while simultaneously handling outside events such as network I/O, or to give the appearance of a prolonged computation happening in the background without affecting UI performance. These are arguably two different shades of the same underlying problem, but how you address these two problems will differ slightly.
The last code example in the previous case is an example of the first case: on a sufficiently fast network with a server that delivers a lot of data to the application, the processor will spend a great deal of time in the DoRead function. On a multithreaded platform, this wouldn’t be a problem, because the network interface would be synchronous and the read operation would run in another thread (and you'd have to trust that the platform would allocate adequate CPU resources to the rest of your application, including user input/output).
That simply isn't possible in BREW. Instead, you need to hand tune the behavior of your callbacks. This is where experience plays a big part; be prepared to spend time benchmarking algorithm performance on a few different handsets (not the BREW Simulator on your PC!), tuning things like read or write buffer sizes to meet your needs. Fortunately, once you've made your interfaces asynchronous, this is all you need to dotest and tune to be sure that the application executes with the performance you'd like to see balanced between user interface and other operations.
Hopefully, the second case is rarecompute-bound applications on wireless terminals aren't terribly popular with most consumers anyway. If you have this problem, though, the solution is to break your long computations up into a series of states, and chain the states using ISHELL_Resume or IThread. The process is similar to how you convert synchronous interfaces into asynchronous ones, but it's often more work, because what may need to be broken up may be inside a loop, and you will be responsible for unrolling the loop into an asynchronous operation.
A common place you may need to do this is in managing a database such as a messaging store; if you need to compact a database or expire old elements (such as in a cache) you should do it like this:
static int Expire( SContext *p, PFNNOTIFY pfnDone, void *pu )
if ( p->pfnDone ) return EWOULDBLOCK;
p->expireNext = GetFirstElement( p );
CALLBACK_Init( &p->pcbExpire, (PFNNOTIFY)ExpireOne, p );
CALLBACK_Init( &p->pcbExpireDone, pfnDone, pu );
ISHELL_Resume( p->pishell, &p->pcbExpire );
static void ExpireOne( SContext *p )
if ( !p ) return;
ExpireIfTooOld( p->expireNext );
p->expireNext = GetNextElement( p );
if (p->expireNext )
// Process the next one.
ISHELL_Resume( p->pishell, &p->pcbExpire );
// We’re done! Tell the client.
CALLBACK_Cancel( &p->pcbExpire );
ISHELL_Resume( p->pishell, &p->pcbExpireDone );
Whew! While it's not nearly as pretty as a while loop in its own thread, it's the only game in town.
Note: To critics of single-threaded platforms, it's also worth noting that after a late evening debugging a deadlocked application with multiple threads, it starts to look a lot better! Threads aren’t simple either; they just move the complexity somewhere else.)
This code enumerates over the items being expired, and processes each one in its own callback invocation; the system will invoke ExpireOne when the processor is idle. Because it does so only when the system is idle, your application can continue to handle other user events; if a user presses a key while the cycle of chained ISHELL_Resumes is pending, the keystroke is delivered to the event. You can also update the user interface (say, to provide a status notification), or provide additional status from this chain of execution by adding additional callbacks or sending messages to your event handler.
Being Prepared Eliminates Drudgery
Porting isn't hard, but it is work. Fortunately, like a lot of work, it's very repetitive, and these patterns appear often. By seeing them now, you are better equipped to make short work of much of the drudgery of bringing your algorithms to Qualcomm BREW.