I have an event in one thread and need to invoke handler function in another. Usually I use function connect(), but in case of two threads I had an error:
QObject::connect: Cannot queue arguments of type 'QVector<unsigned char>'
(Make sure 'QVector<unsigned char>' is registered using qRegisterMetaType().)
I tried to use qRegisterMetaType(), but don't understood clearly how and where should I declare it.
I wrote code sample, which just invoke execution in thread0 from thead1. I didn't include my attempts of using qRegisterMetaType() because they all failed =)
thread0.h:
#ifndef THREAD0_H
#define THREAD0_H
#include <QThread>
#include <QVector>
class thread0 : public QThread
{
Q_OBJECT
public:
thread0();
~thread0();
protected:
void run() Q_DECL_OVERRIDE;
public slots:
void printBuff(QVector<unsigned char> vec);
};
#endif // THREAD0_H
thread1.h:
#ifndef THREAD1_H
#define THREAD1_H
#include <QThread>
#include <QVector>
class thread1 : public QThread
{
Q_OBJECT
public:
thread1();
~thread1();
protected:
void run() Q_DECL_OVERRIDE;
signals:
void sendToPrint(QVector<unsigned char> vec);
};
#endif // THREAD1_H
main.cpp:
#include <QCoreApplication>
#include "thread0.h"
#include "thread1.h"
#include <QObject>
#include <QMetaType>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
thread0 *th0 = new thread0();
thread1 *th1 = new thread1();
QObject::connect(th1, &thread1::sendToPrint, th0, &thread0::printBuff);
th0->start();
th1->start();
return a.exec();
}
and thread1.cpp:
#include "thread1.h"
thread1::thread1()
{
}
thread1::~thread1()
{
}
void thread1::run()
{
QVector<unsigned char> vec = {0, 1, 2, 3};
emit sendToPrint(vec);
}
P.S. If I use direct connection code works.
QObject::connect(th1, &thread1::sendToPrint, th0, &thread0::printBuff, Qt::DirectConnection);
Add this in your main()
qRegisterMetaType<QVector<unsigned char> >("QVector<unsigned char>");
Between threads, Qt uses a Queued Connection. This mechanism requires each type passed as a parameter by the signal to the slot to be known of the Qt meta-object system. Most Qt types are already registered, but not template ones as you need one qRegisterMetaType per specialization of the template
If you use direct connection, your code may seem to work, but printBuff will run in th1, not th0. And in fact, th0 thread will do nothing. This may cause the program to crash if printBuff was designed to run in thread0 only, due to thread safety issues.
Related
Firstly, I tried to use setVisible() from thread
There is an event:
void MainWindow::OnShow(){
// Start OnShow actions
ui->LoadingBox->setVisible(true);
std::thread dThread(OnShow_threaded, ui, &(this->settingsMap));
dThread.join();
}
There is a function OnShow_threaded:
void OnShow_threaded(Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap){
// Connect to server
bool hasInternet = false;
// If app doesn't have Internet access -> show offline mode
if (!hasInternet) {
ui->SettingsLabel->setVisible(true);
}
}
The program crashes when compiling a static assembly with an error:
ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to
objects owned by a different thread. Current thread 0x0x36c56540.
Receiver 'WarningMsg' (of type 'QGroupBox') was created in thread
0x0x341c2fa0", file kernel\qcoreapplication.cpp, line 558
On the line: ui->SettingsLabel->setVisible(true);
At the same time, there is no such error when linking dynamically.
You can find full project on GitHub
Secondly, I tried to use events.
There is a function OnShow_threaded:
void OnShow_threaded(MainWindow* mw, Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap){
// Connect to server
bool hasInternet = false;
// If app doesn't have Internet access -> show offline mode
if (!hasInternet) {
MyEvent* event = new MyEvent(EventTypes::InternetConnectionError);
QCoreApplication::postEvent(mw, event);
//delete event;
//delete receiver;
}
}
There is an event class:
#ifndef EVENTS_HPP
#define EVENTS_HPP
#include <QEvent>
#include <QString>
enum EventTypes {
InternetConnectionError,
Unknown
};
class MyEvent : public QEvent
{
public:
MyEvent(const EventTypes _type) : QEvent(QEvent::User) {_localType = _type;}
~MyEvent() {}
auto localType() const {return _localType;}
private:
int _localType;
};
#endif // EVENTS_HPP
There is an event handler:
void MainWindow::events(QEvent *event)
{
if (event->type() == QEvent::User)
{
MyEvent* postedEvent = static_cast<MyEvent*>(event);
if (postedEvent->localType() == EventTypes::InternetConnectionError){
ui->WarningMsg->setVisible(true);
ui->SettingsLabel->setVisible(true);
}
}
}
Passing parameters:
void MainWindow::OnShow(){
// Start OnShow actions
ui->LoadingBox->setVisible(true);
std::thread dThread(OnShow_threaded, this, ui, &(this->settingsMap));
dThread.detach();
}
There is a mainwindows hpp file:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDebug>
#include <QMovie>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QMessageBox>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QCoreApplication>
#include <QSaveFile>
#include <QProcess>
#include <thread>
#include <chrono>
#include <unordered_map>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "settings.hpp"
#include "events.hpp"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void OnShow();
private slots:
void SettingsLabelPressed();
void on_CloseMsgButton_clicked();
void on_Settings_SaveButton_clicked();
void on_Settings_UseTranslation_stateChanged(int arg1);
protected:
void events(QEvent* event);
private:
Ui::MainWindow *ui;
std::unordered_map<QString,QString> settingsMap;
};
void OnShow_threaded(MainWindow* mw, Ui::MainWindow *ui, std::unordered_map<QString,QString> *settingsMap);
#endif // MAINWINDOW_H
But event doesn't execute.
What did I do wrong?
And how to properly change the GUI from another thread?
З.Ы. Sorry for my English, I'm from Russia....
As you asked for the demo with QThread, in the comments, then here it is.
As GUI, I have a mainwindow with two simple buttons and I want to show, hide the big buttons with a QThread (instead of just the slot clicked) I emit an intermediate signal from the clicked to hide/show the button.
The role of the QThreadis only to emit the signal to sigShowHide with argument trueor false.
The main UI thread treats this signal by showing or hiding the button by calling the slot onShowHideButtonThreaded which reacts to the signal sigShowHide
Here are the code files:
mainwindows.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void sigShowHide(bool);
public slots:
void onShowHideButtonThreaded(bool);
void onButton1Click();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindows.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QObject::connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::onButton1Click);
QObject::connect(this,&MainWindow::sigShowHide, this, &MainWindow::onShowHideButtonThreaded);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onShowHideButtonThreaded(bool a)
{
qDebug() << " the main thread id = " << QThread::currentThread() << "set the visibility ";
ui->pushButton_2->setVisible(a);
}
void MainWindow::onButton1Click()
{
qDebug()<< "clicked";
qDebug() << " the main thread id = " << QThread::currentThread();
QThread* l_thread = QThread::create([&]()
{
qDebug() << "Running Thread " << QThread::currentThreadId() << " to emit signal only ";
emit sigShowHide( !this->ui->pushButton_2->isVisible());
});
l_thread->start();
}
`
The main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
An example of execution is :
the main thread id = QThread(0x116ee18)
Running Thread 0x1ed8 to emit signal only
the main thread id = QThread(0x116ee18) set the visibility
In Qt, like in many other GUI frameworks, the GUI may be updated from the main thread only. This means if you want to update the GUI from another thread, you have to communicate that to the main thread, which in turn will update the GUI.
For more details refer to these articles:
Multithreading in Qt
Qt: Threading Basics
Qt: Multithreading Technologies
Additional resources in other languages: Правильная работа с потоками в Qt.
As stated in #rustyx's answer: In Qt, like in many other GUI frameworks, the GUI may be updated from the main thread only.
I was also stuck on this problem, and here are my two solutions:
Use QMetaObject::invokeMethod.
QMetaObject::invokeMethod(ui->SettingsLabel, "setVisible", Q_ARG(bool, true));
QMetaObject::invokeMethod is a thread-safe API, it has a Qt::ConnectionType type parameter, which has a default value Qt::AutoConnection.
Descriptions of some Qt::ConnectionType values:
Qt::AutoConnection:
(Default) If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
Qt::DirectConnection:
The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.
Qt::QueuedConnection:
The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
Qt::BlockingQueuedConnection:
Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.
It is clear from the description that the API will put your call request into a queue, and then the main thread will take it out from the queue to finally process your call request.
And if you need to get the return value of the target method, you need to explicitly pass Qt::BlockingQueuedConnection to the Qt::ConnectionType type parameter. Because the indirect default value Qt::QueuedConnection will not wait for the call to complete, you will likely get the wrong return value.
But this solution has a trouble that is it only supports signal and slot functions, NOT non-signal and non-slot functions.
Use connect.
// In "MainWindow" class declaration
//
class MainWindow : public QMainWindow
{
// ...
Q_SIGNALS:
void setSettingsLabelVisibleSafety(bool value);
// ...
};
// In "MainWindow" constructor
//
connect(this, &MainWindow::setSettingsLabelVisibleSafety, this,
[this](bool value) {
ui->SettingsLabel->setVisible(value);
}
);
// In other thread
//
setSettingsLabelVisibleSafety(value);
connect also has a Qt::ConnectionType type parameter, which has a default value Qt::AutoConnection if you pass the receiver parameter explicitly, otherwise the default value is Qt::DirectConnection. So you need to either explicitly pass the receiver parameter or explicitly pass Qt::AutoConnection value to the Qt::ConnectionType type parameter.
If the target method has a return value, Qt::BlockingQueuedConnection needs to be passed explicitly as well.
This solution supports signal and slot functions, as well as non-signal and non-slot functions.
Both of the above solutions require you to call qRegisterMetaType to register the user-defined types (if any).
qRegisterMetaType<YourType>("YourType");
Obviously, I prefer the second solution.
What I am trying to achieve is a cross-platform TCP socket library built on top of Qt QTcpServer/Socket. I faced an issue that signals, emitted from a non-Qt thread without Qt event loop, are not received by objects in QThread with the event loop.
I have found that emitting from a non-Qt thread worked before with Qt::QueuedConnection connection type set explicitly, according to this and this questions. Those questions are rather old and relate to Qt 4. So I wonder if this functionality is still supported in Qt 5.
I have explored the Qt 5 source code and found:
Emitting a signal is just a call to QMetaObject::activate
QMetaObject::activate, in turn, calls queued_activate, if connection type is set to Qt::QueuedConnection or the current thread (emitter thread) is different from the thread receiver lives in (in my case, Qt::QueuedConnection is set explicitly).
queued_activate creates an event object and calls QCoreApplication::postEvent
QCoreApplication::postEvent does proper locking and puts the event into the receiver event queue. Despite postEvent is a static QCoreApplication function that uses self - a pointer to current static QCoreApplication singleton, it should work properly even if there is no global QCoreApplication object (i.e. self == 0).
Given this, I suppose that for signal&slot mechanism to work properly, only the receiver thread has to have the Qt event loop that will dispatch the event from the queue, correct me if I am wrong.
Despite that, emitting a signal from a non-Qt thread does not work for me. I have created as simple demo app as possible that demonstrates the malfunctioning of the signal&slot.
MyThread component just inherits QThread and moves inside itself (moveToThread) QObject-derived ThreadWorker.
MyThread.h:
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include "ThreadWorker.h"
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread();
signals:
void mySignal();
private:
ThreadWorker m_worker;
};
#endif // MYTHREAD_H
MyThread.cpp:
#include "MyThread.h"
#include "ThreadWorker.h"
MyThread::MyThread()
: m_worker(*this)
{
m_worker.moveToThread(this);
}
Thread worker is needed to live in MyThread thread and to connect to MyThread`s mySignal() signal.
ThreadWorker.h:
#ifndef THREADWORKER_H
#define THREADWORKER_H
#include <QObject>
class MyThread;
class ThreadWorker : public QObject
{
Q_OBJECT
public:
explicit ThreadWorker(const MyThread& thread);
public slots:
void mySlot();
};
#endif // THREADWORKER_H
ThreadWorker.cpp:
#include "ThreadWorker.h"
#include <QDebug>
#include "MyThread.h"
ThreadWorker::ThreadWorker(const MyThread& thread)
: QObject(0)
{
connect(&thread, SIGNAL(mySignal()),
this, SLOT(mySlot()),
Qt::QueuedConnection);
}
void ThreadWorker::mySlot()
{
qDebug() << "mySlot called! It works!";
}
Finally, the main.cpp:
#include <QCoreApplication>
#include <QDebug>
#include "MyThread.h"
int main(int argc, char *argv[])
{
// QCoreApplication a(argc, argv);
MyThread my_thread;
my_thread.start();
emit my_thread.mySignal();
qDebug() << "mySignal emitted";
my_thread.wait();
// return a.exec();
}
Note, that if I uncomment QCoreApplication creation, I get the correct output:
mySignal emitted
mySlot called! It works!
but if I leave it as is, I get only
mySignal emitted
QEventLoop: Cannot be used without QApplication
So, what is the reason signal&slot mechanism does not work in this case? How to make it working?
The error message tells you exactly what you need to know: you can't use the event loop system without QCoreApplication existing. That's all. All of your exploration into the innards of Qt was educational, but a red herring. None if it matters at all.
only the receiver thread has to have the Qt event loop that will dispatch the event from the queue
That's correct.
Does it mean that if I create QCoreApplication inside QThread, this system should work?
You might create it on any thread (in contrast to QGuiApplication that can only live on the main thread). But make sure that you link statically with Qt. Otherwise, if you're linking with system Qt, you'll become binary incompatible with any process using the same Qt if you create a second instance of the application. Thus, if you use system Qt you can work around by inspecting whether an application instance exists, and only create one if it doesn't exist yet.
Furthermore, you shouldn't really need to create the application instance in a custom thread. Your library should accept an initialization call that should be performed in the main thread of the calling process. This initialization can create an application object if one doesn't exist.
// https://github.com/KubaO/stackoverflown/tree/master/questions/twothreads-41044526
#include <QtCore>
// see http://stackoverflow.com/questions/40382820
template <typename Fun> void safe(QObject * obj, Fun && fun) {
Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
if (Q_LIKELY(obj->thread() == QThread::currentThread()))
return fun();
struct Event : public QEvent {
using F = typename std::decay<Fun>::type;
F fun;
Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
~Event() { fun(); }
};
QCoreApplication::postEvent(
obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}
class Worker : public QObject {
Q_OBJECT
QBasicTimer m_timer;
int n = 0;
void timerEvent(QTimerEvent *event) override {
if (event->timerId() == m_timer.timerId())
emit hasData(n++);
}
public:
Q_SIGNAL void hasData(int);
Q_SLOT void onData(int d) { qDebug() << QThread::currentThread() << "got data" << d; }
void start() {
safe(this, [this]{ m_timer.start(50,this); });
}
void quit() {
safe(this, [this]{ m_timer.stop(); thread()->quit(); });
}
};
class Library {
QByteArray dummy{"dummy"};
int argc = 1;
char *argv[2] = {dummy.data(), nullptr};
QScopedPointer<QCoreApplication> app;
static Library *m_self;
struct {
Worker worker;
QThread thread;
} m_jobs[3];
public:
Library() {
Q_ASSERT(!instance());
m_self = this;
if (!qApp) app.reset(new QCoreApplication(argc, argv));
for (auto & job : m_jobs) {
job.worker.moveToThread(&job.thread);
job.thread.start();
job.worker.start();
QObject::connect(&job.worker, &Worker::hasData, &m_jobs[0].worker, &Worker::onData);
}
}
~Library() {
for (auto &job : m_jobs) {
job.worker.quit();
job.thread.wait();
}
}
static Library *instance() { return m_self; }
};
Library *Library::m_self;
// API
void initLib() {
new Library;
}
void finishLib() {
delete Library::instance();
}
int main()
{
initLib();
QThread::sleep(3);
finishLib();
}
#include "main.moc"
I have develop an app in Qt/C++, the app is currently working in one thread and and based on a UI and a framework. the UI and framework discuss together using the signal/slot mecanism provided by Qt. I have added threading because some operation inside the framework freeze UI.
The issue I'm facing is that now the app is not working anymore because of
QObject::connect: Cannot queue arguments of type 'uint32_t'
(Make sure 'uint32_t' is registered using qRegisterMetaType().)
What I have done is, declare in the main the qRegisterMetatype but it still complaining. I don't really know what I have to do in addition.
I have gave you below the basic process for a deletion for example.
main.cpp
qRegisterMetaType<uint32_t>("uint32_t");
qRegisterMetaType<QList<QTreeWidgetItem*> >("QList<QTreeWidgetItem*>");
Treewidget.cpp
void TreeWidget::DeleteAction() {
....
connect(this,&PulsTreeWidget::RequestDelete, &m_Framework, &PulsFramework::DeleteItem);
emit RequestDelete(item, nb_items);
}
Framework.cpp
void Framework::DeleteItem(QList<QTreeWidgetItem *> item, uint32_t total_item) {...}
Any idea ? do I need to not use the uint32_t as it is but change it's definition
Thanks
You are doing something worng. My example:
Object.h
#ifndef OBJECT_H
#define OBJECT_H
#include <QtCore>
class Object : public QObject
{
Q_OBJECT
public:
explicit Object(QObject *parent = 0);
void emitSignal(uint32_t v);
public slots:
void mySlot(uint32_t v);
signals:
void mySignal(uint32_t v);
};
#endif // OBJECT_H
Object.cpp
#include "Object.h"
Object::Object(QObject *parent) :
QObject(parent)
{
}
void Object::emitSignal(uint32_t v)
{
emit mySignal(v);
}
void Object::mySlot(uint32_t v)
{
qDebug() << v;
}
main.cpp
#include "Object.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
qRegisterMetaType < uint32_t >("uint32_t"); // <--------------
Object o1, o2;
QThread t1, t2;
o1.moveToThread(&t1);
o2.moveToThread(&t2);
QObject::connect(&o1, SIGNAL(mySignal(uint32_t)), &o2, SLOT(mySlot(uint32_t)));
QObject::connect(&o2, SIGNAL(mySignal(uint32_t)), &o1, SLOT(mySlot(uint32_t)));
t1.start();
t2.start();
o1.emitSignal(5);
o2.emitSignal(10);
return app.exec();
}
Output:
5
10
When removing qRegisterMetaType < uint32_t >("uint32_t"); line, also complains:
QObject::connect: Cannot queue arguments of type 'uint32_t'
(Make sure 'uint32_t' is registered using qRegisterMetaType().)
QObject::connect: Cannot queue arguments of type 'uint32_t'
(Make sure 'uint32_t' is registered using qRegisterMetaType().)
P.S.: I don't like your connect+emit pair. You do know, that you need to connect signal to slot only once in order it to work? You do not need to do it before each signal emit.
I have a class that should run in a thread and needs an event loop for the slots, currently I run it nicely with moveToThread(), but I'd like to use QThreadPool and I have encountered a problem.
When run with QThreadPool the run() method of my runnable is called from a pooled thread (I check this with QThread::currentThread()), but my slots aren't running in the pooled thread, so I think the object isn't moved to a thread in the pool.
I think this because I know the slots are run in the receiver's thread, which is exactly the (correct) behaviour I get when using the moveToThread() method and a QThread.
How do I get my QRunnable (Foo in the example below) to be run entirely in the pooled threads?
Or is it something I'm doing wrong or understood wrong?
The following POC demonstrates the problem:
foo.h
#ifndef FOO_H
#define FOO_H
#include <QObject>
#include <QRunnable>
#include <QEventLoop>
class Foo : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit Foo(int data, QObject *parent = 0);
void run();
signals:
void startWorking();
public slots:
void doWork();
private:
QEventLoop eventLoop;
int data;
};
#endif // FOO_H
foo.cpp
#include "foo.h"
#include <QThread>
#include <QDebug>
Foo::Foo(int d, QObject *parent) :
QObject(parent), eventLoop(this), data(d)
{
}
void Foo::run()
{
qDebug() << "run() in: " << QThread::currentThread();
connect(this, SIGNAL(startWorking()), this, SLOT(doWork()));
emit startWorking();
eventLoop.exec();
}
void Foo::doWork()
{
qDebug() << "doWork() in: " << QThread::currentThread();
}
main.cpp
#include <QCoreApplication>
#include <QThreadPool>
#include "foo.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Foo *foo = new Foo(42);
QThreadPool::globalInstance()->start(foo);
return a.exec();
}
Please note, however, that in my real code the signal won't be emitted right away, because it will be after I receive some data on the network.
PS: The POC can also be found here.
Maybe you could split your logic in class Foo into two:
the hoster QRunnable with a QEventLoop, and a worker QObject, which you create on the worker thread in run() before calling QEventLoop::exec method. Then you forward all the signals to the worker object.
So now your slots will be called on the pooled thread.
However, QThreadPool is designed for executing lots of short tasks without creating too many simultaneous threads. Some tasks are enqueued and are waiting for others to finish. If this is not your intention, you might want to go back to good old QThread and use it instead.
You can support both modes but it will require some coordination from the outside. My strategy is to emit a signal from inside QRunnable::run passing the current thread. When you plan to use it in a thread pool, use a Qt::BlockingQueuedConnection on this signal and do your moveToThread there. Otherwise, move it to the QThread and emit a signal to start working as usual.
TaskRunner.h
#pragma once
#include <QObject>
#include <QRunnable>
#include <QThread>
class TaskRunner : public QObject, public QRunnable
{
Q_OBJECT
public:
TaskRunner(int data, QObject* parent = nullptr);
void run() override;
Q_SIGNALS:
void start();
void starting(QThread*);
void stop();
private:
int data;
};
TaskRunner.cpp
#include "TaskRunner.h"
#include <QEventLoop>
#include <stdexcept>
TaskRunner::TaskRunner(int data, QObject* parent)
: QObject(parent), data(data)
{
// start should call run in the associated thread
QObject::connect(this, &TaskRunner::start, this, &TaskRunner::run);
}
void TaskRunner::run()
{
// in a thread pool, give a chance to move us to the current thread
Q_EMIT starting(QThread::currentThread());
if (thread() != QThread::currentThread())
throw std::logic_error("Not associated with proper thread.");
QEventLoop loop;
QObject::connect(this, &TaskRunner::stop, &loop, &QEventLoop::quit);
// other logic here perhaps
loop.exec();
}
main.cpp
#include <QCoreApplication>
#include <QThreadPool>
#include "TaskRunner.h"
// comment to switch
#define USE_QTHREAD
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
auto runner = new TaskRunner(42);
#ifdef USE_QTHREAD
// option 1: on a QThread
auto thread = new QThread(&a);
runner->moveToThread(thread);
QObject::connect(thread, &QThread::finished, runner, &QObject::deleteLater);
Q_EMIT runner->start();
// stop condition not shown
#else
// option 2: in a thread pool
QObject::connect(
runner, &TaskRunner::starting,
runner, &QObject::moveToThread,
Qt::BlockingQueuedConnection);
QThreadPool::globalInstance()->start(runner);
// stop condition not shown
#endif
return a.exec();
}
Since the your connect call
connect(this, SIGNAL(startWorking()), this, SLOT(doWork()));
used the default parameter for connection type, it will be a Qt::Autoconnection.
The signal is emitted from the pooled thread, and the slot still belongs to foo, which has a thread affinity to the main thread. The autoconnection will decide to put the slot in the event queue of the main thread.
There are two ways you can fix this:
1.
connect(this, SIGNAL(startWorking()), this, SLOT(doWork()), Qt::DirectConnection);
and remove the eventloop.exec();
2.
in the run method, move the foo object to the current thread before connecting the signal and slot.
I have some trouble with using enum types in signals. Basicly I have two classes, a state machine and a thread handling the state machine. When the state is changed I want to send a signal with the new state. I also want to represent the state using an enum. In my full blown code the state machine is implemented in a separate shared library, but the code below gives the exact same error.
When I run the code I get the following behaviour:
kotte#EMO-Ubuntu:sigenum $ ./sigenum
Object::connect: No such slot MyThread::onNewState(state)
Test signal
Test signal
...
I have four files in my sample code: statemachine.h, statemachine.cpp, main.h and main.cpp. The main function simply starts the thread, the thread then creates an instance of the StateMachine and processes signals from the StateMachine. I am pretty new to Qt, so I was a bit puzzled when I realised that you have to enclose the enum with Q_ENUMS and register it with the type system. So It's fully possible that I've made some rookie mistake
The code below is a bit long, but I wanted it to be as similar to my real code as possible.
statemachine.h looks like:
// statemachine.h
#ifndef _STATEMACHINE_H
#define _STATEMACHINE_H
#include <QtCore>
class StateMachine : public QObject
{
Q_OBJECT
Q_ENUMS(state)
public:
enum state {S0, S1, S2};
void setState(state newState);
signals:
void stateChanged(state newState);
void testSignal(void);
};
Q_DECLARE_METATYPE(StateMachine::state);
#endif
And it is implemented as:
// statemachine.cpp
#include <QtCore>
#include "statemachine.h"
void StateMachine::setState(state newState)
{
emit stateChanged(newState);
emit testSignal();
}
The thread is defined as
// main.h
#ifndef _MAIN_H
#define _MAIN_H
#include <QtCore>
#include "statemachine.h"
class MyThread : public QThread
{
Q_OBJECT
private:
void run(void);
private slots:
void onNewState(StateMachine::state);
void onTestSignal(void);
private:
StateMachine *myStateMachine;
};
#endif
And it is implemented as follows:
// main.cpp
#include <QtCore>
#include <QApplication>
#include "statemachine.h"
#include "main.h"
void MyThread::run()
{
myStateMachine = new StateMachine();
qRegisterMetaType<StateMachine::state>("state");
// This does not work
connect(myStateMachine, SIGNAL(stateChanged(state)),
this, SLOT(onNewState(state)));
// But this does...
connect(myStateMachine, SIGNAL(testSignal()),
this, SLOT(onTestSignal()));
forever {
// ...
myStateMachine->setState(StateMachine::S0);
}
}
void MyThread::onTestSignal()
{
qDebug() << "Test signal";
}
void MyThread::onNewState(StateMachine::state newState)
{
qDebug() << "New state is:" << newState;
}
By using fully qualified names everywhere I got it to work
If I change the declaration of stateChanged() to
signals:
void stateChanged(StateMachine::state newState);
And registers the type with
qRegisterMetaType<StateMachine::state>("StateMachine::state");
and also uses this name in the connect statement
connect(myStateMachine, SIGNAL(stateChanged(StateMachine::state)),
this, SLOT(onNewState(StateMachine::state)));
Wouldn't have solved this without the input from drescherjm, thanks :-)
I believe the following is state is not defined in your MyThread class.
Use the following
connect(myStateMachine, SIGNAL(stateChanged(StateMachine::state)),
this, SLOT(onNewState(StateMachine::state)));
Edit:
Maybe this will work
connect(myStateMachine, SIGNAL(stateChanged(state)),
this, SLOT(onNewState(StateMachine::state)));
You should get rid of SIGNAL and SLOT since Qt can detect mismatches at compile time. You can also avoid having to use Q_DECLARE_METATYPE and qRegisterMetaType() by using Q_ENUM instead of Q_ENUMS - this was introduced in Qt 5.5, Finally enum class is a strongly typed version of enum:
// statemachine.h
#ifndef _STATEMACHINE_H
#define _STATEMACHINE_H
#include <QtCore>
class StateMachine : public QObject
{
Q_OBJECT
Q_ENUM(state)
public:
enum class state {S0, S1, S2};
void setState(state newState);
signals:
void stateChanged(state newState);
void testSignal(void);
};
#endif
// main.cpp
#include <QtCore>
#include <QApplication>
#include "statemachine.h"
#include "main.h"
void MyThread::run()
{
myStateMachine = new StateMachine();
connect(myStateMachine, &StateMachine::stateChanged, this, &MyThread::NewState);
connect(myStateMachine, &StateMachine::testSignal, this, &MyThread::onTestSignal);
forever {
// ...
myStateMachine->setState(StateMachine::S0);
}
}