PDA

View Full Version : Worker thread problem



hkvm
6th September 2009, 15:17
Hi everyone,

I need some help with this problem I have... I'm trying to make a worker thread perform a long operation and have the GUI stay responsive. I need to give the thread a chunk of data to work on and let it run. I've read that the best way to pass data inbetween threads is signals and slots, so what I did is I subclassed a QThread like this (slightly simplified example: the thread gets a vector of floats and returns an image):


class Worker : public QThread
{
Q_OBJECT
public:
void run();
signals:
void done(QImage);
public slots:
void makeImage(std::vector<float>);
};

void Worker::run()
{
exec();
}

The run() function just launches the event loop and at the end of makeImage slot I call exit(), I only need it to process the one signal. When the thread is done working it passes the result with the "done" signal. The vector type is registered with qRegisterMetaType() so it can be used with QueuedConnection.

Now the (possibly) problematic code:

Worker* worker = new Worker();
connect(this, SIGNAL(makeImage(std::vector<float>)), worker, SLOT(makeImage(std::vector<float>)), Qt::QueuedConnection);
connect(worker, SIGNAL(done(QImage)), this, SLOT(newImage(QImage)), Qt::QueuedConnection);
worker->start();
emit makeImage(my_vector);

I call for QueuedConnection explicitly to make sure the slot is called from the new thread.
The problem is: it doesn't seem like a new thread is started at all, the GUI is blocked and when I call QThread::currentThread() from the working function, the result is the same as from the GUI thread! How come it's not spawning a new thread to do the work? If what I'm doing can be done better please tell me.

wysota
6th September 2009, 17:44
You're overcomplicating things. Try this:

class Thread : public QThread {
Q_OBJECT
public:
void Thread(QObject *parent = 0) : QThread(parent){}
void setData(const std::vector<float> &dat){ m_d = dat; }
void run(){ m_img = makeImage(m_d); }
QImage image() const { return m_img; }
private:
std::vector<float> m_d;
QImage m_img;
};
//...
Thread *t = new Thread;
t->setData(someData);
connect(t, SIGNAL(finished()), this, SLOT(threadFinished()));
t->run();
//...
void ...::threadFinished(){ Thread *thr = qobject_cast<Thread*>(sender());
Q_ASSERT(thr);
QImage image = thr->image();
thr->deleteLater();

If you have multiple chunks to work on, there is a much simpler version:

QImage makeImage(const std::vector<float> &data){
// ...
}
//...
QList<std::vector<float> > input;
QFuture<QImage> result = QtConcurrent::mapped(input, makeImage);
QFutureWatcher<QImage> *watcher = new QFutureWatcher(this);
connect(watcher, SIGNAL(finished()), this, SLOT(operationFinished())); // or some other signal
watcher->setFuture(result);

wysota
6th September 2009, 18:07
Hmm... actually you can simplify the first approach like this:

QImage makeImage(const std::vector<float> &dat) {
//...
}
// ...
QFuture<QImage> future = QtConcurrent::run(makeImage, myData);
QFutureWatcher *watcher = new QFutureWatcher(this);
watcher->setFuture(future);
connect(watcher, SIGNAL(finished()), this, SLOT(...)));

hkvm
6th September 2009, 18:53
Woah this QtConcurrent stuff is mighty powerful, love Qt :D. Works perfecly now, thanks!

Just a few corrections if more people use this: QFutureWatcher is also templated, so it should be QFutureWatcher<QImage>.
And in the first suggested approach:

Thread *t = new Thread;
t->setData(someData);
connect(t, SIGNAL(finished()), this, SLOT(threadFinished()));
t->run();
Calling run() directly doesn't actually spawn a new thread, start() should be called instead and it runs run() in a thread (I tried this approach before using QtConcurrent, woked too but took much more boilerplate code).

wysota
6th September 2009, 21:12
Yeah, sorry, I didn't test this code prior to posting it :)