02.24.09

Async or swim

Posted in Development, Mobile, Symbian, Uncategorized, c++ at 11:53 pm by Twm

In this article, I talk about the problems of wrapping asynchronous function to make them look synchronous. I first focus on the asynchronous APIs in Symbian OS and how synchronous waiting can be implement, and then discuss how Qt(TrollTech) have solved the same problem.
(This is still a bit rough, so I may make a few changes)

Consider the following API:

CTelephone::GetPhoneId(TRequestStatus &aReqStatus, TDes8 &aId) const;

To anyone who has done Symbian programming, the TRequestStatus immediately gives it away as an asynchronous function.
One of the things that really gets on developers tits when starting to program on Symbian is that many calls to do seemingly basic things are asynchronous. In our example, the GetPhoneId() function call returns immediately and an ‘active object’ must be created in order to catch the completion of the function.

Active objects are quite cool for some things, but are a pain because they force you to structure your application as a state machine which is not always an option, For example when porting some standard C++ code which needs to call through to CTelephony::GetPhoneId() to fetch the IMEI from a Synchronous function.

Ok, so there is one way of solving this. Calling User::WaitForRequest() forces the current thread to wait until the request has completed, which means you should do something like this:

CTelephony* telephony = CTelephony::NewLC()
TRequestStatus status;
TBuf buffer;
telephony->GetPhoneId(status, buffer);
User::WaitForRequest(status); <- blocks till status is completed
User::LeaveIfError(status.Int()); <- Int() is KErrNone if the call was successful

Great, now we can return the IMEI to the caller without messing around with state machines.
Wrong! This code blocks the thread forever, the request will never complete.

Active Object Chaining

The User::WaitForRequest() function only works as expected if the status is completed by a thread other than the context in which it’s called. This is true for RSocket, where the reads and writes are immediately handed over to the socket server.
User::WaitForRequest() will block the thread permanently if the TRequestStatus needs to be completed by something running in the same thread.

This condition occurs when the asynchronous function uses active objects internally, either because it’s chaining several asynchronous calls together in state machine and then only completing the TRequestStatus passed in on termination of the state machine. Or the asynchronous call has split up a lengthy operation into a number of smaller steps (see splitting long running tasks with active objects here).

This is problematic since by eye alone: It’s not possible to tell if it’s safe to call User::WaitForRequest() on any particular asynchronous function and requires a posteriori knowledge.

CActiveSchedulerWait
At some point during Symbian’s history. It was acknowledged that even when you strive to use asynchronous state machines, there are some situations where this is difficult (as mentioned above), and there really needs to be a solution which allows a thread to wait for any asynchronous call.
CActiveSchedulerWait was born and made available to 3rd parties.

The waiter has some problems, but it’s possible to use it to approximate synchronous behaviour – i.e call a set of asynchronous functions (including those which use AOs internally) one after each other in a function and return a final value.
I say approximate because what CActiveSchedulerWait actually does is create a nested scheduler loop which means that all other active objects in the thread get a chance to run, not just the one you are waiting for.
In a UI environment, this means that key presses, pointer events, socket read/write completions etc may all complete and call other function in your app, all while you are waiting for the one asynchronous function to complete. Not dealing with this often leads to obscure bugs. At the very least you should signal the AppUi() to not process any commands until the waiter has returned. It’s all very messy.

How does Qt do it?
I was curious as to how other environments which support asynchronous functions deal with this.
Qt already has a mature way of signalling completion of an operation with its system of signals and slots. This already cuts down the need to create a special class simply for handling the completion of an asynchronous method, you simply declare a method in any class which can handle completion and connect that to the signal which will be emitted when the asynchronous method has completed.

But that’s pretty much replacing state machine with callbacks. Is there a way to wait within a function for a asynchronous method to complete?

The solution is very similar to CActiveScheduler::Wait()

QEventLoop eventLoop;
connect(this, SIGNAL(TwmsAsyncFunctioncompleted()), &eventLoop, SLOT(quit()));
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);

What that does is start a nested eventloop and connects the signal TwmsAsyncFunctioncompleted from the asynchronous provider to the quit() slot of the QEventLoop. And like magic, on competion the event loops exits without any extra code from us.
Of course, Qt still has the same problem that with a nested event loop, User input events are still processed, but Qt allows you to delay the processing of input and repainting events (and even socket completions) by specifying QEventLoop::ExcludeUserInputEvents.
All that happens is that they are buffered and processed the next time around the top level event loop.

2 Comments »

  1. Matt Davies said,

    February 26, 2009 at 1:32 pm

    Hmm, I wonder what they do in Python for S60 VM.

  2. Twm said,

    February 26, 2009 at 4:00 pm

    Generally, python calls are synchronous or rely on callbacks. For the most part, calls which block the thread for a long time should be run in their own thread.
    On Py60, you can run something like a game loop, but must call ao yield() to yield to the active scheduler to let UI events and other asychronous active objects to run.

Leave a Comment

You must be logged in to post a comment.