Topic : Asynchronous programming using Qt
Author : Jan Borsodi
Page : 1

Asynchronous programming using Qt





Author: Jan Borsodi
Publishing date: 07.12.2001 18:06


Qt already comes with an asynchronous system called events, however it is more or less bound to a set number of message types. In larger GUI applications one might be interested in something which scales better. I'll explain one such method.

All though the code is written using Qt the same principles will work with other tool-kits.

Most asynchronous methods around involve using messages which are sent around the system. Each message is sent around to all clients listening and they then either accept the message or ignores it. This is usually done by incorporating a message type in the message body which clearly distinguishes different message types from one another. Each client must then examine the message and do an action depending on the type. The drawback with this is adding new message types requires all clients to be updated, thus making it impossible to use in a dynamic system or any large sized application.

In this article I'll show another method involving the Visitor pattern, or specifically the Acyclic Visitor pattern, which allows for true dynamic behavior. By using the acyclic visitor we can easily add new message types without the clients needing any source update or recompiling. Our base message will look something like this.


class Visitor
{
public:
    Visitor() {}
    virtual ~Visitor() {}
};

class Message
{
public:

    virtual void accept( Visitor &v ) = 0;

};



As we can see the message interface has one virtual function called accept which takes a Visitor as a parameter. This function is called by the message manager for each visitor with the visitor as the parameter. Since this is just an interface we need to inherit it and create a new class. Let's create a message which stores a string.

class StringMessage
{
public:

    const QString &text() const
    {
        return Text;
    }

    virtual void accept( Visitor &v )
    {
        StringVisitor *sv = dynamic_cast<StringVisitor *>( &v );
        if ( sv )
            sv->visitString( &this );
    }

private:
    QString Text;
};

class StringVisitor
{
public:

    virtual void visitString( const StringMessage &msg ) = 0;
};



As you can see each new message has a visitor interface associated with it which needs to be inherited and implemented by all visitors in the system. For instance one could do

class StringHandler : public Visitor, public StringVisitor
{
public:

    virtual void visitString( const StringMessage &msg )
    {
        // Do something with the message
    }
};



This means that for each message type you want a class to support you inherit from the visitor interface. You might find it strange to actually encourage multiple inheritance, but as you can see we're simply adding new interfaces to our class, this is perfectly safe. The other objection you might have is the dynamic_cast which relies on the RTTI of the c++ language. You could add a unique ID to the message and check for that but then you would loose the dynamic behavior we were looking for.

Now that we have a proper message system we can start to look at the asynchronous part. What we want is a manager which we dispatch a message to and have it sent to all interested visitors. We want the message handler call to return instantly. For that we use a queue and a timer. The timer is connected to the processQueue function.

class Manager
{
public:

    void handleMessage( Message *msg )
    {
MessageQueue.enqueue( msg );
        MessageTimer->start( 0, true );
    }

private slots:
    void processQueue()
    {
        if ( MessageQueue.isEmpty() )
            return;
        Message *msg = MessageQueue->dequeue( msg );
        for ( QListIterator<Visitor> it( Visitors ); it.current(); ++it )
        {
            Visitor *c = it.current();
            msg->accept( *c );
        }
delete msg;
    }

private:
    QQueue<Message>  MessageQueue;
    QTimer          *MessageTimer;
    QList<Visitor>   Visitors;
};



As you can see the handleMessage function instantly returns, it just adds the message to the queue and starts a timer. The timer value is 0 meaning that it will time-out as soon as the event loop is entered. When the processQueue function is called each visitor is traversed and passed as an argument to the message function accept.

One important thing to remember is that the client visit functions must not take too much time, they should copy any necessary data and return. If they need to some more processing they should use the QTimer trick used in the manager or spawn a separate thread to the work.

Now you have a general framework for asynchronous message handling, some interesting extensions to it could be:

- Add a unique id, either a string or integer, to each client which is sent with the message. That way receiving clients can reply to messages or forward them.
- Add support for threads, make a mutex which controls access to the message queue, that way threads can leave messages in the queue.
- Message forwarding with an XMLRPC interface.
- Add more information into the base message, that way the manager can discriminate on the content of the message and choose which clients should get it or not.
- Use templates for the message/visitor part, that way you don't need to create a new visitor for each new message type.

Page : 1