Code This

std::cout <<me.ramble() <<std::endl;

Archive for the ‘example’ Category

Using QThread Without Subclassing

with 28 comments

The source code for this blog post can be found here. You do not need a Pro account to download the file, click No if you are prompted to upgrade.

Having been developing with C++ and Qt for around 6 years, and being a regular on #qt on freenode, a thought occurred to me. Hey, why not use that experience for a blog post? So, I thought I would start with one of the least understood classes in the Qt API, and that is QThread. This class is inherently difficult to use, because threading is difficult. Threading can turn a perfectly deterministic system into something non-deterministic. A threading issue is typically going to be much more difficult to diagnose and correct than other issues. Don’t get me wrong, threading is very useful, and serves a very important purpose (imagine using a web browser that didn’t use threads to fetch and render the content). But you should be familiar with both the positives and the negatives of the tools you are using to develop applications.

The first thing you need to know about using QThread is, a QThread object is not a thread. QThread is a class used for managing a thread; it is not itself a thread. The only method on QThread that runs on a separate thread is “run”. The second thing you need to know about using QThread is, as with all QObject subclasses, a QThread object has affinity to the thread in which it is instantiated. If you subclass QThread and add your own slots, you may be surprised to learn that these slots will actually run on the main thread. This also means you cannot use the QThread object as the parent of a QObject instance that has affinity to the thread (i.e., one created within “run”). And lastly, you need to know that only the main (GUI) thread can access GUI elements (QWidgets). The method of communicating between the thread and the GUI is signals and slots. If you don’t know, by default an object’s slots are run on the thread to which the object has affinity. This is done using the event loop.

Unfortunately, until Qt 4.4, QThread was an abstract class. The “run” method was pure virtual. And, even though this is no longer the case, the documentation still shows an example of using QThread by subclassing. While this is not wrong, or bad, it is not necessary. It also lends itself to using the class in ways it was not designed to be used. QThread is now able to be used without being subclassed. Attached is a small example application that creates a QTreeWidget and a button. Clicking the button will lead to the tree widget being populated with the contents of the user’s home direcrtory. Reading the contents of the file system is done on a separate thread. For this reason, you will see that you can still interact with the GUI while this is being done. There is a “sleep” call added, and tree widget items are expanded and collapsed while this is happening, so you are able see what is happening while the application runs.

So let’s jump into some code. This is the declaration of the FSReader class, the class that reads from the file system:

#ifndef FSREADER_H
#define FSREADER_H

#include <QObject>

class FSReader : public QObject
{
   Q_OBJECT

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

public Q_SLOTS:
   void start();

Q_SIGNALS:
   void pathFound(const QString& path);
   void finished();
};

#endif // FSREADER_H

A pretty typical QObject subclass. As we will see later, the start slot will be executed when the thread begins to run, and the finished signal is emit’d when the object has completed its task. The pathFound signal is used to communicate with the GUI. The following is the implementation of the class:

#include "FSReader.h"
#include <QDirIterator>
#include <QDesktopServices>

#if defined(Q_OS_WIN32)
#include <windows.h>
#define SLEEP(msecs) Sleep(msecs)
#else
#define SLEEP(msecs) usleep(msecs * 1000)
#endif

FSReader::FSReader(QObject* parent)
   : QObject(parent)
{
}

FSReader::~FSReader()
{
}

void FSReader::start()
{
   QDirIterator it(QDesktopServices::storageLocation(QDesktopServices::HomeLocation), 
      QDirIterator::Subdirectories);
   while (it.hasNext()) {
      emit pathFound(it.next());
      SLEEP(1);
   }
   emit finished();
}

As you can see, the implementation of this class is quite simple. It only uses QDirIterator to iterate the files and directories in the user’s home directory, and emits a signal for each one found. As mentioned previously, the SLEEP call is simply to slow the program down a bit so you can see what is happening while it runs. It should not be used in a “real” application.

The last code snippet is also the most relevant, as it includes the use of QThread:

void Widget::onBtnClicked()
{
   QThread* thread = new QThread;
   FSReader* reader = new FSReader;

   reader->moveToThread(thread);

   connect(thread, SIGNAL(started()), reader, SLOT(start()));
   connect(reader, SIGNAL(pathFound(QString)), this, SLOT(onPathFound(QString)));
   connect(reader, SIGNAL(finished()), thread, SLOT(quit()));
   connect(reader, SIGNAL(finished()), reader, SLOT(deleteLater()));
   connect(reader, SIGNAL(finished()), thread, SLOT(deleteLater()));

   thread->start();
}

This is the button click handler of the Widget class. You can see here we instantiate both a QThread and FSReader instance, each without a parent. We then change the affinity of the “reader” to this new thread using the method “moveToThread”. This method can be used to change the thread affinity of any QObject.

Lets look at each connection that follows. The first is what starts the reader object after the thread has started. This method is run on the thread. The second connect links the reader object to the GUI. The “onPathFound” slot is run on the main thread. The next connect causes the thread’s event loop to exit once the reader has finished. The default implementation of QThread::run simply starts the thread’s event loop. The last 2 connects handle cleanup, since our objects were created without parents. The “deleteLater” method causes the objects to be deleted once there are no more pending events in the event loop. The reader is deleted on the thread, and the thread itself is actually deleted on the main thread. This “deleteLater” call works for the reader, even though the thread’s event loop would have already exited, because QThread does one final loop to handle all deferred deletetion events once its event loop has exited. QApplication does the same.

The rest of the Widget class, and the main, is included in the linked source, and is not particularly relevant to this post. That’s all you have to do to use QThread without subclassing it. Happy threading!

Advertisements

Written by Kris Wong

April 4, 2011 at 12:22 pm