Synchronized Threads (Part 2)

The astute observer will notice a problem in the code I provided for Synchronized Threads (Part 1), which is that the eventloop begins only after we unlock the mutex.

Why is that bad? Well, this means that the calling thread will resume before the eventloop in the SyncThread has started. If the calling thread then calls stop() right away, it is possible that the “quit” method of the QEventLoop object will be delivered too early. If this happens, then the eventloop won’t actually quit, and the calling thread will be stuck forever. Additionally, code within atStart() occurs without an eventloop running. These two problems are nearly identical to those covered in the startmyappLater article, which is why I published it before this one.

Correcting this problem in SyncThread takes a little bit of effort. We are going to need a helper QObject that runs in our thread, which I’ve called SyncThreadAgent.

class SyncThread : public QThread
{
        Q_OBJECT
private:
        QMutex m;
        QWaitCondition w;
        QEventLoop *loop;
        SyncThreadAgent *agent;

public:
        SyncThread(QObject *parent = 0);
        ~SyncThread();

        void start();
        void stop();

protected:
        virtual void run();
        virtual void atStart() = 0;
        virtual void atEnd() = 0;

private slots:
        void agent_started();
};

class SyncThreadAgent : public QObject
{
        Q_OBJECT
public:
        SyncThreadAgent(QObject *parent = 0) : QObject(parent)
        {
                QMetaObject::invokeMethod(this, "started", Qt::QueuedConnection);
        }

signals:
        void started();
};

SyncThread::SyncThread(QObject *parent)
:QThread(parent)
{
        loop = 0;
        agent = 0;
}

SyncThread::~SyncThread()
{
        stop();
}

void SyncThread::start()
{
        QMutexLocker locker(&m);
        Q_ASSERT(!loop);
        QThread::start();
        w.wait(&m);
}

void SyncThread::stop()
{
        QMutexLocker locker(&m);
        if(!loop)
                return;
        QMetaObject::invokeMethod(loop, "quit");
        w.wait(&m);
        wait();
}

void SyncThread::run()
{
        m.lock();
        loop = new QEventLoop;
        atStart();
        w.wakeOne();
        m.unlock();
        agent = new SyncThreadAgent;
        connect(agent, SIGNAL(started()), SLOT(agent_started()), Qt::DirectConnection);
        loop->exec ();
        m.lock();
        atEnd();
        delete agent;
        agent = 0;
        delete loop;
        loop = 0;
        w.wakeOne();
        m.unlock();
}

void SyncThread::agent_started()
{
        atStart();
        w.wakeOne();
        m.unlock();
}

Now everything should be correct.

Stay tuned, we will do more with SyncThreadAgent in Part 3.

Post comment as twitter logo facebook logo
Sort: Newest | Oldest