PDA

View Full Version : Seeking design advise - part 1



TorAn
7th March 2019, 20:35
This is the development based on my question here (https://qtcentre.org/threads/70057-Seeking-design-advise)

My problem now is that I can't get the signal directoryChanged of the class QFileSystemWatcher to work with the slot in the thread where QFileSystemWatcher is created. Following is the code:
Any help with getting it to work correctly (catch the signal) will be greatly appreciated.
Thanks!


class threadedScraper : public QThread
{
Q_OBJECT
int _id;
QMutex& _mtx;
QString _path;
QFileSystemWatcher* _filesystemwatcher;
void run() override
{
_filesystemwatcher = new QFileSystemWatcher();
_filesystemwatcher->moveToThread(this);
_filesystemwatcher->addPath(_path);

// I tried different connection types. Qt::ConnectionType::DirectConnection does not work either
if (! connect(_filesystemwatcher, SIGNAL(directoryChanged(const QString&)), this, SLOT(ondirectoryChanged(const QString&))))
{
qDebug() << "failure to connect to QFileSystemWatcher::directoryChanged signal";
return;
}

qDebug() << QString("cycle %1: waiting for file creation in %2").arg(_id).arg(_path);
exec();
qDebug() << QString("cycle %1: stopped waiting for file").arg(_id);
}
private slots:
void ondirectoryChanged (const QString& p)
{
qDebug() << "cycle:" << QString::number(_id) << "file:" <<p;
_mtx.unlock();
}
public:
threadedScraper(int id, QMutex& m, QString p) : _id(id), _mtx(m), _path(p), _filesystemwatcher(nullptr), QThread()
{
}
virtual ~threadedScraper()
{
if (_filesystemwatcher != nullptr)
delete _filesystemwatcher;
}
};

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

QMutex mtx;
QString directoryToMonitor="c:/temp/files";

QWaitCondition wc;
QTimer::singleShot(2000,&a, [&](){
qDebug()<< "started scraping";
for (int i = 0; i < 2; ++i) {
qDebug() << "starting cycle " << QString::number(i);
threadedScraper worker (i, mtx, directoryToMonitor);
worker.start();
mtx.lock();
wc.wait(&mtx, 5000); // I need to wait until the file is found in the monitoring directory or 5 seconds elapsed
worker.exit(0); // stop the thread
worker.wait();
mtx.unlock();
}
qDebug()<< "finished scraping";
});
return a.exec();
}

d_stranz
7th March 2019, 21:40
My problem now is that I can't get the signal directoryChanged of the class QFileSystemWatcher to work

What about other signals? Do they work?

TorAn
7th March 2019, 22:46
I don't know, I only used this one, directoryChanged. I don't think that it is the issue with signal firing, I am pretty sure that all this signals work. I think that somehow the qFileSystemWatcher instance still "lives" in the main thread and not in the thread where I am creating it and moving it into.
the code is compilable, if you have 5 mins you can run it, or I can post the project here, it is tiny.

TorAn
8th March 2019, 03:07
Problem solved. Thanks.

Ginsengelf
8th March 2019, 08:19
It would be nice to tell us how you solved your problem.

Ginsengelf

TorAn
8th March 2019, 19:06
I did not solve the issue with the code that I posted. I rewrote the code, following is the version that works:



class scraperProcess: public QObject, public QRunnable
{
Q_OBJECT
QMutex& _mtx;
QWaitCondition& _wc;
public:
scraperProcess(QMutex& mtx, QWaitCondition& w) : _mtx(mtx), _wc(w){}
void run()
{
qDebug()<< "started scraping";
for (int i = 0; i < 3; ++i) {
qDebug() << "cycle starts: " << QString::number(i);
_mtx.lock();
_wc.wait(&_mtx, 5000); // I need to wait until the file is found in the monitoring directory or 5 seconds elapse
_mtx.unlock();
qDebug() << "cycle ends: " << QString::number(i);
}
qDebug()<< "finished scraping";
emit onFinish();
}
signals:
void onFinish();
};


int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

QMutex mtx;
QWaitCondition wc;
QFileSystemWatcher _filesystemwatcher;
QString directoryToMonitor="c:/temp/files";
_filesystemwatcher.addPath(directoryToMonitor);
if (! QObject::connect(&_filesystemwatcher, &QFileSystemWatcher::directoryChanged, [&](const QString& p){
qDebug() << "scraped file:" <<p;
wc.wakeAll();
})) {
qDebug() << "failure to connect to QFileSystemWatcher::directoryChanged signal";
}

scraperProcess* ms = new scraperProcess(mtx, wc);
QObject::connect(ms, &scraperProcess::onFinish, [&](){
_filesystemwatcher.removePath(directoryToMonitor);
});

QTimer::singleShot(2000,&a, [&](){
QThreadPool::globalInstance()->start(ms);
});

return a.exec();
}

I think I have an idea what might be the problem with the previous versions. It is "singleShot". This is essentially a wrapper for handling the event in the event loop. My previous code was "sitting" in it, preventing other events from being processed. That's my theory.

I always wonder how to start something in the console application. The "singleShot" is what comes to mind first. Perhaps, posting an event. I'd like to see the "proper" way of doing this.

Thanks to everyone who read and/or post comments.

anda_skoa
9th March 2019, 10:04
void run() override
{
_filesystemwatcher = new QFileSystemWatcher();
_filesystemwatcher->moveToThread(this);


moveToThread() is not necessary, the QFileSystemWatcher object is created in run(), thus by the secondary thread.





// I tried different connection types. Qt::ConnectionType::DirectConnection does not work either
if (! connect(_filesystemwatcher, SIGNAL(directoryChanged(const QString&)), this, SLOT(ondirectoryChanged(const QString&))))


Be aware that ondirectoryChanged() will be executed on the main thread, i.e. the thread that created the threadedScraper object.
I.e. AutoConnection behaves like QueuedConnection if the emitting thread and the receiver object's thread are not the same.





virtual ~threadedScraper()
{
if (_filesystemwatcher != nullptr)
delete _filesystemwatcher;
}


The _filesystemwatcher object got created in run(), by the secondary thread, so it should be deleted by that thread.
E.g. at the end of run().





wc.wait(&mtx, 5000); // I need to wait until the file is found in the monitoring directory or 5 seconds elapsed


You always wait for 5 seconds because the other thread has no access to the wait condition and can't wake the main thread.

Not really sure why you need such a complicated multi thread setup if you don't do any work in the primary thread anyway.



My previous code was "sitting" in it, preventing other events from being processed. That's my theory.

Yes, because your original code blocked the main thread in a wait condition.



The "singleShot" is what comes to mind first.

If there is no external trigger than this is a good solution.

In this case unnecessary as you have an external trigger: the file system watcher.

Cheers,
_

TorAn
9th March 2019, 13:56
anda_skoa - thank you for your responses.

You wrote:
Be aware that ondirectoryChanged() will be executed on the main thread, i.e. the thread that created the threadedScraper object.

Can you explain why? The instance of fileSystemWatcher is created in run method of threadedScraper instance, so why would ondirectoryChanged slot be executed in the context of the main thread and not in the event loop of the threadedScraper?

Thank you again for answering!

anda_skoa
9th March 2019, 21:49
Can you explain why?

Certainly :)



The instance of fileSystemWatcher is created in run method of threadedScraper instance, so why would ondirectoryChanged slot be executed in the context of the main thread and not in the event loop of the threadedScraper?

The connect() uses four arguments, meaning the fifth argument is the default value Qt::AutoConnection

For AutoConnection the emitting thread checks if it is the same thread as the one which "owns" the receiver object.
If it is, then it will behave like in a DirectConnection, it will call the slot directly.
If it is not, then it will behave like in a QueuedConnection, it will send a method invocation event to the receiver object's thread.

Here the emitting thread is the secondary thread, the "owner" of the receiver object "worker" is the main thread.
So case 2, behavior like a QueuedConnection.

Cheers,
_