How to handle multiple asynchronous requests with a wait loop

This document describes how to handle multiple asynchronous requests with a wait loop.

A user thread which requires asynchronous services from more than one asynchronous service provider uses a wait loop.

The allows a thread to issue as many request as are necessary and to handle their completion. This construction forms the heart of almost all programs that run under Symbian platform.

A typical program:

  • waits for any request to complete by waiting on the thread’s request semaphore.

  • scans in turn through the asynchronous service providers for which a request is known to have been issued and tests the relevant TRequestStatus for completion; this is identified by a value other than KRequestPending.

  • when the first such completed request has been identified, handles the completed request, possibly by issuing a further request to an asynchronous service provider.

  • goes back to the top of the wait loop and waits again.

As an example, a terminal emulator must work with multiple requests simultaneously, to deal with:

  • key presses and user input in general, to control the action of the program and put characters on the emulated terminal screen and/or send them to the remote host

  • received data and to put characters on the emulated terminal screen or perform some control function.

  • the completion of any transmit operation that was in progress; if successful, there may be more data to transmit; if unsuccessful, some recovery action may be necessary

Typical wait loop protocol

The typical protocol of a wait loop is:

  • The initialization phase which issues the first asynchronous requests.

  • Entry into the main loop which waits for any of these requests to complete and then handles the completion of one of them.

  • Detection of conditions which imply termination of the program. Typically, one of the request completion handlers sets appropriate flags and the wait loop terminates.

  • Cleanup and final termination.

initialize;
while (!finished())
    {
    wait for one or more requests to complete
    decide which request completed, and handle it
    }
terminate;

Note that only one request should be handled per iteration of the loop. As part of the request completion handling process, the request may be re-issued, or new requests issued or existing ones cancelled. If the request cannot be identified, then the thread’s request semaphore has been signalled invalidly; this is indicative of a programming error, a condition is known as a stray signal - the wait loop should raise a panic.

Multiple outstanding requests

In the case of a terminal emulator which works with multiple outstanding requests, it must be able to service whichever request completes and must be able to re-issue a request, if necessary. For example, when one key has been processed, a request for the next key must be issued.

In the following example:

  • Call Initialize() to initialize the emulator.

  • Implement while() to handle events.

  • Call the function User::WaitForAnyRequest() to wait for any request to complete instead of waiting for a specific request to complete.

  • Identify the particular request that completed by checking the request status objects for a value not equal to KRequestPending.

class TerminalEmulator
    {
public:
    void MainLoop(); // main loop
private:
    void Initialize(); // initialize
    void Terminate(); // clean up and terminate
    TBool IsFinished(); // whether finished
private:
    // request status objects
    TRequestStatus iKeyPressed; // whether key pressed
    ...                         // ...etc for other request status
    // handler functions
    void HandleKeyPressed();    // handle key pressed
    ...                         // ... etc other handlers
    ...
    };
void TerminalEmulator::MainLoop()
    {
    Initialize();         // Initialize emulator
    while (!IsFinished()) // Handle events until finished
        {
        // wait for any request to complete
        User::WaitForAnyRequest();

        // identify and handle the completed request
        if (iKeyPressed!=KRequestPending)
            {
            HandleKeyPressed();
            }
        else if (iReceiveCompleted!=KRequestPending)
                {
                HandleReceiveCompleted();
                }
            else if (iTransmitCompleted!=KRequestPending)
                    {
                    HandleTransmitCompleted();
                    }
                else
                    {
                    // something we weren’t expecting
                    // panic !
                    }
        }
    }

Note

  • The request status is set by the service provider to KRequestPending when a request is issued. When the request is complete, the service provider uses an operating system call to set the request status value to some other value — usually a standard error code — and then to wake up the thread that requested the service, causing its WaitForAnyRequest() to complete.