Reputation: 23
I am trying to call a slot in a different object with its own thread and eventloop, but for some reason the slot which is called is still the GUI main thread and not the thread belonging to the object. What am I doing wrong? I did everything the documentations said to do, any hints would be relay appreciated. Ger.
The called ImportSpectrumFile is from my main GUI from Place a breakpoint in slotOpenFile(QString path_file_name) and seen it the main GUI thread ID
Dispatcher.cpp
#include "dispatcher.h"
#include <QMessageBox>
Dispatcher::Dispatcher()
{
initApplications();
initSignalsAndSlots();
}
void Dispatcher::initApplications()
{
file_import = new File_Import();
file_import->start();
}
void Dispatcher::initSignalsAndSlots()
{
connect(this, SIGNAL(signalOpenFile(QString)), file_import, SLOT(slotOpenFile(QString)),Qt::QueuedConnection);
}
void Dispatcher::ImportSpectrumFile(QString path_file_name)
{
// This is the main GUI thread
Qt::HANDLE id = QThread::currentThreadId();
signalOpenFile("File name ger");
}
Dispatcher.h
#ifndef DISPATCHER_H
#define DISPATCHER_H
#include <QObject>
#include "convert_spectrum.h"
#include "file_import.h"
class Dispatcher : public QObject
{
Q_OBJECT
public:
Dispatcher();
void ImportSpectrumFile(QString path_file_name);
private:
void initApplications();
void initSignalsAndSlots();
File_Import * file_import;
signals:
void signalOpenFile(QString path_file_name);
private slots:
};
#endif // DISPATCHER_H
file_import.c
#include "file_import.h"
File_Import::File_Import(QThread *parent) : QThread(parent)
{
}
void File_Import::slotOpenFile(QString path_file_name)
{
// This is the same thread ID as the main GUI thread
// This should be the thread in this object?
// I called exec below, event loop is running for this thread
// Why is this the main GUI thread id ?
Qt::HANDLE id = this->currentThreadId();
}
void File_Import::run()
{
// Enter thread event loop
exec();
}
file_import.h
#ifndef FILE_IMPORT_H
#define FILE_IMPORT_H
#include <QObject>
#include <QThread>
class File_Import : public QThread
{
Q_OBJECT
public:
File_Import(QThread *parent = nullptr);
void run() override;
signals:
public slots:
void slotOpenFile(QString path_file_name);
};
#endif // FILE_IMPORT_H
Upvotes: 0
Views: 833
Reputation: 12898
The problem lies in the way you are using QThread
. Consider the statement...
connect(this, SIGNAL(signalOpenFile(QString)),
file_import, SLOT(slotOpenFile(QString)), Qt::QueuedConnection);
When the signal signalOpenFile
is emitted for this connection Qt
will take action to ensure that slotOpenFile
is called on the thread associated with file_import
. It identifies that thread by calling
file_import->thread();
Unfortunately, because File_Import
inherits from QThread
the thread affinity of file_import
is the QThread
on which was created -- not the newly created thread it manages (i.e. the thread on which the overridden run
member is executing.
The fix is to simply have File_Import
inherit from QObject
and move it to a separate QThread
.
class File_Import: public QObject {
Q_OBJECT;
public:
File_Import(QObject *parent = nullptr);
public slots:
void slotOpenFile(QString path_file_name);
};
And let's assume you add a member to Dispatcher
...
QThread file_import_thread;
Then Dispatcher::initApplications
becomes...
void Dispatcher::initApplications ()
{
file_import = new File_Import;
file_import->moveToThread(&file_import_thread);
file_import_thread.start();
}
Note that you really should be using the new signal/slot syntax...
connect(this, &Dispatcher::signalOpenFile,
file_import, &File_Import::slotOpenFile, Qt::QueuedConnection);
Upvotes: 1
Reputation: 98505
Thankfully, none of this complexity is necessary.
All you need is a QObject
that reads the file etc. Then create a thread for it, and move the object to that thread. At that point, slot calls are automatically converted to events and passed to event queue of the thread the object lives in.
Notice that there’s no need to derive from QThread
, no need to manually start event loops, and no need to manually specify connection types. It’s stupendously simple. Qt does all the work for you :)
As for the structure of the code: typically you want the GUI and the logic to be separate, with the interface between the two only via signal/slot connections. Those connections should be established outside of both objects. For example, in main
, or wherever the receiver/sender are instantiated. The Dispatcher
class is just convoluting things.
In a larger application, instead of signals from a UI objects you’d be using QAction
s, of course.
So, the GUI should look like:
class UI : public QMainWindow {
Q_OBJECT
public:
Q_SIGNAL void loadFile(const QSteing &filename);
Q_SLOT void fileLoaded(const QString &filename, QSharedPointer<FileData> data);
…
};
The loader object does the task of loading files:
Loader : public QObject {
Q_OBJECT
QFile m_file;
…
public:
Loader(QObject *parent = nullptr) : QObject(parent) {}
QSharedPointer<FileData> loadFile(const QString &filename);
Q_SIGNAL fileLoaded(QSharedPointer<FileData>);
};
There’s also some struct FileData
that carries the representation of the data you imported. This is application specific per your requirements.
These are like LEGO building blocks. It’s easy to build your application from them:
int main(int argc, char **argv) {
QApplication app(argc, argv);
UI ui;
Loader loader;
QThread loaderThread;
loader.moveToThread(&loaderThread);
loaderThread.start();
QObject::connect(&ui, loadFile, &loader, &Loader::loadFile);
QObject::connect(&loader, &Loader::fileLoaded, &ui, &UI::FileLoaded);
int rc = app.exec();
QMetaObject::invokeMethod(&loader, [&]{
loader.moveToThread(qApp->thread();
});
loaderThread.requestIntereuption(); // optional
loaderThread.wait();
return rc;
}
Since loading process can be long, and you don’t want it to hold up application exit, you can insert if (currentThread()->isInterruptionRequested()) return;
in long running loops in the file reading code, so that it’d return to the local event loop quickly on an interruption request.
Also note that the loader both returns the file data and emits it via a signal. This is to facilitate testing and debugging: you can just invoke the function directly to test/debug, but any cross-thread users still have a way of interacting with it.
Also note that if the file loading is a single function, it can just be executed in a different thread without all this rigmarole - you’d use QtConcurrent::run
on a lambda that loads the file and then invokes (via QMetaObject::invoke) a method on the UI object. But that requires more care so that the shutdown can be done in an orderly fashion - you definitely wouldn’t want that loading to outlive the UI object, for example.
Upvotes: 0