본문 바로가기
Research/Programming

Worker Thread in Qt using Signals & Slots

by sunnyan 2011. 4. 12.
728x90

origin URL : http://cdumez.blogspot.com/2011/03/worker-thread-in-qt-using-signals-slots.html

Saturday, March 12, 2011

Worker Thread in Qt using Signals & Slots

Qt provides platform-independent threading classes, a thread-safe way of posting events, and signal-slot connections across threads. Multithreaded programming allows taking advantage of multiprocessor machines and it is also useful to perform time-consuming operations without freezing the user interface of an application.

Without multithreading, all the processing is done in the main (UI) thread.

Signal-slot connections across threads
As mentioned earlier, Qt supports connecting signals and slots across threads. This provides an interesting way to pass data between threads.

Let's have a look at the QObject::connect() method:
?
1
2
3
bool QObject::connect(const QObject *sender, const
char *signal, const QObject *receiver, const char
*method, Qt::ConnectionType type = Qt::AutoConnection);

The last parameter is the connection type and it is important to understand it. The default value is Qt::AutoConnection, meaning that if the signal is emitted from a different thread than the receiving object, the signal is queued, behaving as Qt::QueuedConnection. Otherwise, the slot is invoked directly, behaving as Qt::DirectConnection. The type of connection is determined when the signal is emitted.

In our case, we are particularly interested in Qt::QueuedConnection because:
  • It is safe to use a queued connection between two different threads (direct connection is not)
  • The slot is executed in the receiver's thread. This means that we can emit a signal from the main thread and connect it to a slot in our worker thread. The processing is then done in the worker thread.

Simple example
Let's consider a simple example where we would like to sort a vector of integers in a separate worker thread. As a consequence, we should have two threads: the main (UI) thread and the worker thread that takes care of the sorting.

As shown in the following figure, we need two objects, one living in the main thread (Sorter) and one living in the worker thread (SorterWorker). 
A QObject instance is said to live in the thread in which it is created. Events to that object are dispatched by that thread's event loop.
Visual representation of our threads

Let's have a look at the code of our Sorter class. Because we want to create a new thread, the Sorter class will subclass QThread (which inherits QObject). The Sorter class will provide a sortAsync(QVector) method that will be called by clients to sort a vector asynchronously. Because the operation is asynchronous, we also need to define a vectorSorted(QVector) signal to notify the client when the sorting is done.


Sorter class: header
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <QThread>
#include <QVector>
 
/*! Class for doing work (sorting) in a worker thread */
class Sorter : public QThread {
  Q_OBJECT
 
public:
  /*! Default constructor */
  Sorter(QObject *parent = 0);
 
  /*! Sorts asynchronously a vector in a worker thread */
  void sortAsync(const QVector<int> &list);
 
signals:
  /*!
   * Internal signal used to communicate with
   * the worker thread.
   */
  void sortingRequested(const QVector<int> &list);
  /*! Signal emitted once the vector is sorted */
  void vectorSorted(const QVector<int> &list);
 
protected:
  void run();
 
private:
  /*!
   * Boolean indicating if the worker thread is ready
   * to process requests.
   */
  bool m_ready;
};

Here are some details regarding the implementation of the Sorter class.
In the constructor, we start the worker thread (the code in run() will be executed). Before returning, the constructor waits for the worker thread to be ready (i.e. The SorterWorker object has been created and signals/slots were connected) to make sure that the client cannot make sorting requests before the worker thread is ready to process them.

In the run() method, we create the SorterWorker object. It is important that this object is created inside the run() method and not inside the SorterWorker constructor so that it lives in the worker thread (and not in the main thread). We use an internal sortingRequested(QVector) signal to forward the sorting requests to the SorterWorker in the worker thread. Because the signal is emitted in the main thread and because the receiver (SorterWorker) lives in the worker thread, a queued connection will be used. As a consequence, the doSort(QVector) slot will be executed in the worker thread.
Also note the use of QMetaType::qRegisterMetaType() before connecting our signals/slots. Whenever a signal is queued, the parameters must be of types that are known to Qt's meta-object system, because Qt needs to copy the arguments to store them in an event behind the scenes. If you forget this, you will get the following error:
 QObject.connect: Cannot queue arguments of type 'QList<int>'
 (Make sure 'QList<int>' is registered using qRegisterMetaType().)
Sorter class: source
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <QMetaType>
#include <QDebug>
#include "sorter.h"
#include "sorter_p.h"
 
Sorter::Sorter(QObject *parent):
  QThread(parent), m_ready(false) {
  qDebug() << Q_FUNC_INFO << QThread::currentThreadId(); // Main Thread
  // Start the worker thread
  start();
  // Wait for the worker thread to be ready;
  while(!m_ready) msleep(50);
}
 
void Sorter::sortAsync(const QVector<int> &v)
{
  qDebug() << Q_FUNC_INFO << QThread::currentThreadId(); // Main Thread
  emit sortingRequested(v);
}
 
void Sorter::run()
{
  qDebug() << Q_FUNC_INFO << QThread::currentThreadId(); // Worker Thread
  // This QObject lives in the worker thread
  SorterWorker worker; // DO NOT define 'this' pointer as parent
  // We need to register QList<int> because it is not known
  // to Qt's meta-object system
  qRegisterMetaType< QVector<int> >("QVector<int>");
  // Pass sorting requests to SorterWorker in the worker thread
  connect(this, SIGNAL(sortingRequested(QVector<int>)),
  &worker, SLOT(doSort(QVector<int>))/*, Qt::QueuedConnection*/);
  // Forward the signal to the clients
  connect(&worker, SIGNAL(vectorSorted(QVector<int>)), this,
  SIGNAL(vectorSorted(QVector<int>))/*, Qt::QueuedConnection*/);
  // Mark the worker thread as ready
  m_ready = true;
  // Event loop (necessary to process signals)
  exec();
}
</int>

SorterWorker class
The implementation of the SorterWorker class is straightforward. You just need to make sure that it subclasses QObject (do not forget the Q_OBJECT macro so that signals/slots can be used). Worker methods such as doSort(QVector) have to be defined as public slots. In the case that these methods have to return data, use signals to do so. Do not forget to forward the signals to the clients in the Sorter class.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <QObject>
#include <QVector>
#include <QThread>
#include <QDebug>
 
/*! Class doing the actual work (sorting) */
class SorterWorker: public QObject {
  Q_OBJECT
 
signals:
  /*! Signal emitted once the vector is sorted */
  void vectorSorted(const QVector<int> &v);
 
public slots:
  /*! Method taking care of the actual sorting */
  void doSort(const QVector<int> &v) {
    qDebug() << Q_FUNC_INFO << QThread::currentThreadId(); // Worker Thread
    QVector<int> v_sorted = v;
    qSort(v_sorted);
    emit vectorSorted(v_sorted);
  }
};


How to use our worker thread
To use the worker thread, you simply need to create a Sorter Object and make sure you connect its vectorSorted(QVector) signal to a local slot to get the result. You can then call the sortAsync(QVector) method to ask to worker thread to sort your vector.

?
1
2
3
4
Sorter t;
connect(&t, SIGNAL(vectorSorted(QVector<int>)),
        SLOT(handleVectorSorted(QVector<int>)));
t.sortAsync(QVector<int>() << 1 << 3 << 2);

Debugging
To make sure the functions are executed in the correct thread, you can use the following instruction:
1
qDebug() << Q_FUNC_INFO << QThread::currentThreadId();
QThread::currentThreadId() is a static function that returns the id of the current execution thread.

QtConcurrent alternative
I should mention that for simple cases (such as the one I gave as example), QtConcurrent is a suitable alternative which results in less code.

You can use the following function to run a function is a separate thread:
1
QFuture<T> QtConcurrent::run(Function func, ...);
This executes the function func in a separate thread and returns a QFuture Object. You can check if the function is done executing using QFuture::isFinished() method. Alternatively, you can use a QFutureWatcher object to receive a signal when the function is done.

I hope this helps some of you with multithreaded programming in Qt.

6 comments:

nishant said...

I think We dont need to subclass QThread, and this example can be made very simple. Qt devs have already said that subclassing QThread is not a good idea.

Christophe Dumez said...

From the official QThread documentation:
"To create your own threads, subclass QThread and reimplement run()."

Gareth said...

@Christoper: that statement in the docs dates from before Qt 4.4, when the default implementation of QThread::run() was changed so that it now calls QThread::exec() - and so there is no longer any need to subclass QThread.

This is discussed in the following post on Qt Labs (and in the extensive chain of comments below it).

http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/

nishant said...

as rightly pointed out by Gareth, this is the exact post i was refereing to when i said

"Qt devs have already said that subclassing QThread is not a good idea."

This is an old and wrong way of doing things.

shiroki said...

I used the same implementation as the blogger's for many years. Since this is due to a change introduced in 4.4 and the qt document has not been changed to reflect the new design, it is the document to blame.
http://bugreports.qt.nokia.com/browse/QTBUG-16358

Christophe Dumez said...

I know about this blog article. Please note that I'm not using "moveToThread(this);" in my constructor. I believe that my use of QThread is correct although there may be a simpler solution.

What would be a cleaner solution then? If I try to follow the directions in the Qt blog article, I get the following code: http://pastebin.com/N2V4Vdk2
Frankly, I would not say it is any better. Am I missing something?

728x90

'Research > Programming' 카테고리의 다른 글

sqlite 파일, 테이블, 쿼리, 업데이트  (0) 2011.05.18
pthread_cond_wait  (0) 2011.05.11
An Introduction to SQLite  (0) 2010.02.01
Qt, undefined reference to `vtable for  (1) 2010.01.26
Makefile: 변수 출력하기  (0) 2009.12.14