PDA

View Full Version : How to connect signal-slog in second thread?



arcull
16th July 2015, 06:13
Hi everyone.

I'm working on a little desktop gui app, which starts some other thread which would watch for changes in a certain file. I know I don't need a second thread for this, but would really like to, since I would do some time consuming processing on that file change event and would like to keep my main thread where gui app resides, still responsive to user comands.

Ok, so I have my second thread, but the problem is, that I can not connect signal-slot in it. The message I get at run time is "QObject::connect: No such slot QThread::HandleFileChange(const QString)". The relevant part of the code is bellow. Much thanks for help.

worker.h

#ifndef WORKER_H
#define WORKER_H
#include <QThread>
#include <QFileSystemWatcher>

class Worker: public QThread
{
public:
Worker(QString SrcFileParam,QString WorkerFolderParam);

void run();

private:
QString SrcFilename,WorkerFolder;
int Cntr;
QFileSystemWatcher * watcher;

public slots:
void HandleFileChange(const QString fileName);

};

#endif // WORKER_H

worker.cpp

#include "Worker.h"
#include <iostream>
#include <QMessageBox>
#include <QFile>
#include <QFileInfo>

Worker::Worker(QString SrcFileParam,QString WorkerFolderParam)
{
SrcFilename = SrcFileParam;
WorkerFolder = WorkerFolderParam;

watcher = new QFileSystemWatcher(this);
watcher->addPath(this->SrcFilename);

}

void Worker::run() {
connect(watcher,SIGNAL(fileChanged(QString)),this, SLOT(HandleFileChange(const QString))); //problematical line
for (int i=1;i<100;i++) {
//std::cout << qPrintable(i);
//std::cout << "printed from Worker class \nl";
//QMessageBox::information(0,tr("Info"),QString::number(i));
}

}

void Worker::HandleFileChange(const QString fileName) {
// some code
}


mainwindow.cpp


Worker wkr1(ui->lineEdit->text(),ui->lineEdit_2->text());
wkr1.start();
wkr1.wait();

prasad_N
16th July 2015, 07:23
signal is fileChanged ( const QString &) not fileChanged (QString)
and you need to add Q_OBJECT marco in worker class.

one more thing is, if we use signal/slot across threads we need to start local event loop with exec() at the end of the run function;

arcull
16th July 2015, 08:53
Thanks prasad_N, but

signal is fileChanged ( const QString &) not fileChanged (QString) according to codecompletion that is not true.

and you need to add Q_OBJECT marco in worker class. would you please show a short sample how to do it, remember a newbie;)

one more thing is, if we use signal/slot across threads we need to start local event loop with exec() at the end of the run function; I'm not sure I understand this one,should I put exec() in main thread or inside Worker::run. If you can add adjustments to my case I would be really gratefull, much thanks again.

anda_skoa
16th July 2015, 09:13
according to codecompletion that is not true

Correct, just QString is the simplified/normalized argument type signature.



would you please show a short sample how to do it, remember a newbie;)

Q_OBJECT is placed right after the opening brace { of the class declaration



class MyClass : public QObject
{
Q_OBJECT
};




I'm not sure I understand this one,should I put exec() in main thread or inside Worker::run. If you can add adjustments to my case I would be really gratefull, much thanks again.

You only need event loop on the thread that receives the signals. In your case that is the main thread.
The instance of Worker is created by the main thread, thus the slot HandleFileChange(QString) will be executed in the main thread.
The signal source, in this case the file system watcher, is also in the main thread, so its events are also handled there.

The only thing the worker thread does in this example is running a for loop and then exit.

Cheers,
_

arcull
16th July 2015, 12:26
Correct, just QString is the simplified/normalized argument type signature.


Q_OBJECT is placed right after the opening brace { of the class declaration



class MyClass : public QObject
{
Q_OBJECT
};




You only need event loop on the thread that receives the signals. In your case that is the main thread.
The instance of Worker is created by the main thread, thus the slot HandleFileChange(QString) will be executed in the main thread.
The signal source, in this case the file system watcher, is also in the main thread, so its events are also handled there.

The only thing the worker thread does in this example is running a for loop and then exit.

Cheers,
_
Thanks. You are right, that is how it would work now. But I would like the second thread (Worker class) to receive the signals and connect them to correct slot, ie. I would like the watching and handling file changes to be done by the second thread. How could I accieve this? Besides, what would I put in the run method of Worker class in this case, because I need it to "keep alive" and not exit if that is not requested from the main thread. Would an infinite loop be ok? Please suggest, a short snippet of code would be great. Thanks for your help.

anda_skoa
16th July 2015, 13:17
There are three options:

1) treat your run() method like you would treat the main() function, i.e. forget about the encapsulating object.
Just create an instance of the actual worker object and call exec() on the thread.

2) Use the moveToThread approach: create an instance of a QObject derived worker class and an instance of plain QThread and call worker->moveToThread(threadInstance)

3) Create the file system watcher in run() instead of your thread class' constructor and use Qt::DirectConnection when connecting to the slot in your thread class.
Call exec() at the end of run()

Cheers,
_

arcull
16th July 2015, 13:35
Thanks anda_skoa.
1) treat your run() method like you would treat the main() function, i.e. forget about the encapsulating object.
Just create an instance of the actual worker object and call exec() on the thread. This one sounds the easiest ;) So in my case, run() method of Worker class would have just one line of code to connect signal slot? After calling exec() on the thread, will the thread stay alive, that is, would it handle signal slot events? Am I still able to terminate this thread from the first one? Thanks again.

d_stranz
16th July 2015, 16:07
Maybe reading some of the Qt threading examples and tutorials (http://doc.qt.io/qt-5/examples-threadandconcurrent.html) would help you.

prasad_N
16th July 2015, 19:53
may help you this : http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-1/

arcull
17th July 2015, 06:31
Ok, I've solved it with a bit of trial and error. Here is a working sample:
mainwindow.cpp


Worker *wrkr1;
wrkr1 = new Worker(ui->lineEdit->text(),ui->lineEdit_2->text());
wrkr1->start();
workder.h


#ifndef WORKER_H
#define WORKER_H
#include <QThread>
#include <QFileSystemWatcher>
#include <QObject>

class Worker: public QThread
{
Q_OBJECT

public:
Worker(QString SrcFileParam,QString WorkerFolderParam);

void run();

private:
QString SrcFilename,WorkerFolder;
int Cntr;
QFileSystemWatcher * watcher;

public slots:
void HandleFileChange(const QString fileName);

};

#endif // WORKER_H

worker.cpp


#include "Worker.h"
#include <iostream>
#include <QMessageBox>
#include <QFile>
#include <QFileInfo>

Worker::Worker(QString SrcFileParam,QString WorkerFolderParam)
{
std::cout << "Worker constructor \n";
SrcFilename = SrcFileParam;
WorkerFolder = WorkerFolderParam;

watcher = new QFileSystemWatcher();
watcher->addPath(this->SrcFilename);

}

void Worker::run() {
connect(watcher,SIGNAL(fileChanged(QString)),this, SLOT(HandleFileChange(const QString)));
qDebug("method run in Worker thread");
exec();
}

void Worker::HandleFileChange(const QString fileName) {
qDebug("HandleFileChange triggered...");
//some code
}


To sum up, I've made 3 crucial mistakes:
1)forgetting Q_OBJECT macro, without it the Worker thread wont have signal/slot functionality
2)starting the second thread and then waiting.
3)missing exec() in my Worker thread

Now it works,I'm happy. Thank you all for help.

anda_skoa
17th July 2015, 10:01
So in my case, run() method of Worker class would have just one line of code to connect signal slot?

No.
As I wrote, this approach treats run() like main().
There is no slot to connect to unless you create an object that has such a slot.



After calling exec() on the thread, will the thread stay alive, that is, would it handle signal slot events?

Yes.



Am I still able to terminate this thread from the first one?
Yes, simply by calling the thread's quit() method.


Ok, I've solved it with a bit of trial and error. Here is a working sample:

No.
That still processes everything in the main thread.
Your worker thread does nothing, it jus sits there waiting for events that never come.

Cheers,
_

arcull
17th July 2015, 10:35
No.
That still processes everything in the main thread.
Your worker thread does nothing, it jus sits there waiting for events that never come. I don't think you're right...or it may be I don't understand who does what in this case. I tried to run my sample and HandleFileChange method of Worker class executes every time my selected files changes, wouldn't this mean it is executed in Worker class i.e. my second thread?

yeye_olive
17th July 2015, 11:04
I don't think you're right...or it may be I don't understand who does what in this case. I tried to run my sample and HandleFileChange method of Worker class executes every time my selected files changes, wouldn't this mean it is executed in Worker class i.e. my second thread?
No, it means that the connection is correctly set up and that some thread executes the slot when the signal is emitted. Unfortunately for you, the main thread is the one executing the slot.

A QThread manages a thread, but does not live in it; it lives in the thread that allocated it.

In your example, the line

connect(watcher,SIGNAL(fileChanged(QString)),this, SLOT(HandleFileChange(const QString)));
connects two objects, watcher and the Worker instance, with an AutoConnection. As a result, HandleFileChange is executed in the thread in which the Worker instance lives, that is, the main thread. The second thread -- the one managed by the Worker instance -- indefinitely waits for events.

So, just to clarify anda_skoa's proposal #1, which you decided to follow:
Your worker object should not be the same as the QThread object.
Declare two classes: a QThread-derived class MyThread and a QObject-derived class MyWorker.
In MyThread::run(), allocate a MyWorker on the stack and connect some signals to its slots, then run exec().
Since the MyWorker instance was allocated in MyThread::run(), it lives in the thread executing MyThread::run(), i.e. the thread managed by the MyThread instance. That thread will be the one executing the slots of the MyWorker instance, which is exactly what you are after.
By contrast, the MyThread instance itself lives in the main thread.

arcull
17th July 2015, 11:55
ufff :( thanks, I'll try.

anda_skoa
17th July 2015, 13:08
In MyThread::run(), allocate a MyWorker on the stack and connect some signals to its slots, then run exec().

Right, and also allocate the file system watcher there if you want its events to be processed by the secondary thread.

Cheers,
_

arcull
20th July 2015, 08:06
This works, but am not sure the file change is processed by second thread now. Can you please review the code, thanks
worker.h


#ifndef WORKER_H
#define WORKER_H
#include <QThread>
#include <QFileSystemWatcher>
#include <QObject>

class Worker : public QObject
{
Q_OBJECT

public:
Worker(QString SrcFileParam,QString WorkerFolderParam);

private:
QString SrcFilename,WorkerFolder;
int Cntr = 0;
QFileSystemWatcher * watcher;

public slots:
void HandleFileChange(const QString fileName);

};

#endif // WORKER_H

worker.cpp


#include "Worker.h"
#include <iostream>
#include <QMessageBox>
#include <QFile>
#include <QFileInfo>

Worker::Worker(QString SrcFileParam,QString WorkerFolderParam)
{
SrcFilename = SrcFileParam;
WorkerFolder = WorkerFolderParam;

watcher = new QFileSystemWatcher();
watcher->addPath(this->SrcFilename);
connect(watcher,SIGNAL(fileChanged(QString)),this, SLOT(HandleFileChange(const QString)));
}

void Worker::HandleFileChange(const QString fileName) {
qDebug("HandleFileChange triggered...");
}

procthread.h


#ifndef PROCTHREAD_H
#define PROCTHREAD_H

#include <QThread>

class ProcThread : public QThread
{

private:
QString SrcFilename,WorkerFolder;

public:
explicit ProcThread(QString SrcFileParam,QString WorkerFolderParam);

void run();
};

#endif // PROCTHREAD_H

procthread.cpp


#include "procthread.h"
#include "Worker.h"

ProcThread::ProcThread(QString SrcFileParam,QString WorkerFolderParam)
{
SrcFilename = SrcFileParam;
WorkerFolder = WorkerFolderParam;
}


void ProcThread::run() {
Worker wrkr1(SrcFilename,WorkerFolder);
exec();

}

mainwindow.cpp


procth1 = new ProcThread(ui->lineEdit->text(),ui->lineEdit_2->text());
procth1->start();

mikag
20th July 2015, 08:46
The link posted above shows a rather good and easy way to trace what thread executes your code...
Just change the trace in Worker::HandleFileChange and you will know where the code executes, a second trace in mainwindow before starting the thread should give you the main GUI thread id to compare with.


qDebug() << "HandleFileChange triggered... " << QThread::currentThreadId();

Code review even by experts can never replace tracing the code in it's correct environment.

anda_skoa
20th July 2015, 09:02
This works, but am not sure the file change is processed by second thread now. Can you please review the code, thanks

Yes, that looks good, a perfect example of the "treat run() like main()" approach.

Worker doesn't know anything about threads and it would work without change in main():



QCoreApplication app(...);

Worker worker(...);

app.exec();


Cheers,
_

yeye_olive
20th July 2015, 09:08
That looks about right, but there is a memory leak; the QFileSystemWatcher is never deallocated.

arcull
20th July 2015, 09:26
Ok, thanks all again. By the way, how do I mark thread as solved?