Qt signal/slots - consume signal in one of many slots? - qt

Is there a way to consume future slots (and stop them from executing) in one of many slots connected to the same signal?
My goal here is to emit a signal with a message to many QObjects and consume (stopping iteration to future slots) when the QObject in which the message belongs to finds it.
From what I understand in the Qt Documentation:
If several slots are connected to one signal, the slots will be
executed one after the other, in the order they have been connected,
when the signal is emitted.
I want to be able to stop this process from within a slot.
Suggestions?

No, there's no way to do it, and you should not think of it this way. The sender should perform the same no matter what number of slots are connected to a signal. That's the basic contract of the signal-slot mechanism: the sender is completely decoupled from, and unaware of, the receiver.
What you're trying to do is qualified dispatch: there are multiple receivers, and each receiver can process one or more message types. One way of implementing it is as follows:
Emit (signal) a QEvent. This lets you maintain the signal-slot decoupling between the transmitter and the receiver(s).
The event can then be consumed by a custom event dispatcher that knows which objects process events of given type.
The objects are sent the event in the usual fashion, and receive it in their event() method.
The implementation below allows the receiver objects to live in other threads. That's why it needs to be able to clone events.
class <QCoreApplication>
class <QEvent>
class ClonableEvent : public QEvent {
Q_DISABLE_COPY(ClonableEvent)
public:
ClonableEvent(int type) : QEvent(static_cast<QEvent::Type>(type)) {}
virtual ClonableEvent * clone() const { return new ClonableEvent(type()); }
}
Q_REGISTER_METATYPE(ClonableEvent*)
class Dispatcher : public QObject {
Q_OBJECT
QMap<int, QSet<QObject*>> m_handlers;
public:
Q_SLOT void dispatch(ClonableEvent * ev) {
auto it = m_handlers.find(ev->type());
if (it == m_handlers.end()) return;
for (auto object : *it) {
if (obj->thread() == QThread::currentThread())
QCoreApplication::sendEvent(obj, ev);
else
QCoreApplication::postEvent(obj, ev.clone());
}
}
void addMapping(QClonableEvent * ev, QObject * obj) {
addMapping(ev->type(), obj);
}
void addMapping(int type, QObject * obj) {
QSet<QObject*> & handlers = m_handlers[type];
auto it = handlers.find(obj);
if (it != handlers.end()) return;
handlers.insert(obj);
QObject::connect(obj, &QObject::destroyed, [this, type, obj]{
unregister(type, obj);
});
m_handlers[type].insert(obj);
}
void removeMapping(int type, QObject * obj) {
auto it = m_handlers.find(type);
if (it == m_handlers.end()) return;
it->remove(obj);
}
}
class EventDisplay : public QObject {
bool event(QEvent * ev) {
qDebug() << objectName() << "got event" << ev.type();
return QObject::event(ev);
}
public:
EventDisplay() {}
};
class EventSource : public QObject {
Q_OBJECT
public:
Q_SIGNAL void indication(ClonableEvent *);
}
#define NAMED(x) x; x.setObjectName(#x)
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
ClonableEvent ev1(QEvent::User + 1);
ClonableEvent ev2(QEvent::User + 2);
EventDisplay NAMED(dp1);
EventDisplay NAMED(dp12);
EventDisplay NAMED(dp2);
Dispatcher d;
d.addMapping(ev1, dp1); // dp1 handles only ev1
d.addMapping(ev1, dp12); // dp12 handles both ev1 and ev2
d.addMapping(ev2, dp12);
d.addMapping(ev2, dp2); // dp2 handles only ev2
EventSource s;
QObject::connect(&s, &EventSource::indication, &d, &Dispatcher::dispatch);
emit s.indication(&ev1);
emit s.indication(&ev2);
return 0;
}
#include "main.moc"

If connection was in one thread, I think that you can throw an exception. But in this case you should be catch any exception during emit a signal:
try {
emit someSignal();
} catch(...) {
qDebug() << "catched";
}
But I think that it's bad idea. I'll would be use event dispatching for this.

Related

Qt postEvent() and event filter

I want to filter some unwanted events sent to my worker QThread by QCoreApplication::postEvent(...), before it actually processed in event loop.
When is event actually filtered by event filter: in postEvent() in the calling thread or later in QThread event loop?
I think answer is "in event loop", but i couldn't find answer to that exact question in qt docs.
Both the worker and its event filter need to be living in the same thread. Events are picked up by the thread's event loop and passed through the filter right before being delivered to their receiver QObject (when the filter allows this). Here is an example that demonstrates this behavior:
#include <QtCore>
//a thread that can be destroyed at any time
//see http://stackoverflow.com/a/25230470
class SafeThread : public QThread{
using QThread::run;
public:
explicit SafeThread(QObject *parent = nullptr):QThread(parent){}
~SafeThread(){ quit(); wait(); }
};
struct MyEvent : public QEvent {
static const QEvent::Type myType = static_cast<QEvent::Type>(2000);
MyEvent():QEvent(myType){}
};
//worker QObject that handles MyEvent by printing a message
class Worker : public QObject {
public:
explicit Worker(QObject *parent = nullptr):QObject(parent){}
bool event(QEvent *event) {
if(event->type() == MyEvent::myType) {
qInfo() << "event in thread: " << QThread::currentThreadId();
return true;
}
return QObject::event(event);
}
};
class EventFilter : public QObject {
public:
explicit EventFilter(QObject *parent = nullptr):QObject(parent){}
bool eventFilter(QObject *watched, QEvent *event) {
if(event->type() == MyEvent::myType) {
qInfo() << "filter in thread: " << QThread::currentThreadId();
return false; //do not filter the event
}
return QObject::eventFilter(watched, event);
}
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
//create worker and thread
Worker worker;
EventFilter filter;
SafeThread thread;
filter.moveToThread(&thread);
worker.moveToThread(&thread);
worker.installEventFilter(&filter);
thread.start();
qInfo() << "calling postEvent from thread: " << QThread::currentThreadId();
QCoreApplication::postEvent(&worker, new MyEvent());
QTimer::singleShot(1000, &a, &QCoreApplication::quit);
return a.exec();
}
Output should look something like:
calling postEvent from thread: 0xAAAA
filter in thread: 0xBBBB
event in thread: 0xBBBB
The filtering must be performed on event delivery, because the filter expects the target object to exist and have an accessible state. Such existence and state can only be guaranteed while the event is being delivered. It is not safe to use QObject's methods, save a select few, from other threads, thus in general when posting an event, it's not possible to safely access the target object!. It's only possible to do so when the object's lifetime is guaranteed, and thread-safe methods are used to access the state, and it's done in a fashion that doesn't lead to deadlocks.

QT signal error: "this" is unavailable for static member function

I'm working at a socket class for my application that will introduce me in QT framework. When I try to build I get this error: 'this' is unavailable for static member functions.
This is my class .h and .cpp
#pragma once
#include <QObject>
class QTcpSocket;
namespace Ps{
class InstSocket : public QObject
{
Q_OBJECT
public:
InstSocket(QObject *parent=0);
bool Connect();
bool isOpen();
void Disconnect();
//Geters
QString GetHostName() const {return m_hostName;}
quint16 GetPort() const {return m_port;}
//seters
void SetHostName(const QString& value);
void SetPort(quint16 value);
void SetLongWaitMs(int value){m_longWaitMs = value;}
void SetShortWaitMs(int value){m_shortWaitMs = value;}
void WriteData(const QString &data) const;
~InstSocket();
QString ReadData() const;
signals:
static void NotifyConnected();
static void NotifyDisconnected();
private slots:
void onConnected();
void onDisconnected();
private:
//this holds a reference to QtcpSocket
QTcpSocket& m_socket;
QString m_hostName;
quint16 m_port;
int m_shortWaitMs;
int m_longWaitMs;
explicit InstSocket(const InstSocket& rhs) = delete;
InstSocket& operator= (const InstSocket& rhs) = delete;
};
}
and the cpp:
#include "instsocket.h"
#include "QTcpSocket"
#include "QDebug"
#include "utils.h"
namespace Ps
{
InstSocket::InstSocket(QObject *parent) :
QObject(parent),
m_socket(*new QTcpSocket(this)),
m_hostName(""),
m_port(0),
m_shortWaitMs(0),
m_longWaitMs(0)
{
/* my signals are wired to the undelying socket signals, the signal connected is triggered, when a conection
* is established. This will be wired to onConnected and Disconnected slots*/
connect(&m_socket, &QTcpSocket::connected, this, &InstSocket::onConnected);
connect(&m_socket, &QTcpSocket::disconnected, this, &InstSocket::onDisconnected);
}
bool InstSocket::Connect()
{
qDebug() << "attempting to connect to "<< m_hostName << "on port" << m_port << "with wait time: "<<m_longWaitMs;
m_socket.connectToHost(m_hostName, m_port, QTcpSocket::ReadWrite);
return m_socket.waitForConnected(m_longWaitMs);
}
bool InstSocket::isOpen()
{
return m_socket.isOpen();
}
void InstSocket::Disconnect()
{
if(!isOpen()) return;
m_socket.disconnectFromHost();
}
void InstSocket::onConnected()
{
emit NotifyConnected();
}
void InstSocket::onDisconnected()
{
emit NotifyDisconnected();
}
void InstSocket::SetHostName(const QString &value)
{
m_hostName = value;
}
void InstSocket::SetPort(quint16 value)
{
m_port = value;
}
void InstSocket::WriteData(const QString &data) const
{
/*support for writeing to socket. The write metod of the socket will return the number of bites writen*/
int bytes_written = m_socket.write(qPrintable(data));
qDebug() << "Bytes written: "<<bytes_written;
}
QString InstSocket::ReadData() const
{
if(!m_socket.isReadable())
{
return "ERROR: Socket is unreadable.";
}
QString result;
//until the socket reports there is no data available
while(!m_socket.atEnd())
{
result.append(m_socket.readAll());
/*since typically a PC would be much faster at reading than an instrument might be at writing
* instrument must have a chance to queue up more data in case the message it's sending us is long.*/
m_socket.waitForReadyRead(m_shortWaitMs);
}
return result;
}
InstSocket::~InstSocket()
{
Utils::DestructorMsg(this);
}
}
and this is the error:
Qt Projects\build-Vfp-Desktop_Qt_5_7_0_MSVC2015_64bit-Debug\debug\moc_instsocket.cpp:-1: In static member function 'static void Ps::InstSocket::NotifyConnected()':
error: 'this' is unavailable for static member functions
QMetaObject::activate(this, &staticMetaObject, 0, Q_NULLPTR); In static member function 'static void Ps::InstSocket::NotifyDisconnected()':
error: 'this' is unavailable for static member functions
QMetaObject::activate(this, &staticMetaObject, 1, Q_NULLPTR);
When I clicked on them, QT creator took me to moc_instsocket.cpp (that is in build folder and poit to this:
// SIGNAL 0
void Ps::InstSocket::NotifyConnected()
{
QMetaObject::activate(this, &staticMetaObject, 0, Q_NULLPTR);
}
// SIGNAL 1
void Ps::InstSocket::NotifyDisconnected()
{
QMetaObject::activate(this, &staticMetaObject, 1, Q_NULLPTR);
}
I can't figure out what to do althought I checked all the code several times. There is no need to know about utils class since there are just some debug messages. Did anyone know how to fix it?
What is the meaning of static signal? In Qt signals and slots are used on object level.
signals:
static void NotifyConnected();
static void NotifyDisconnected();
All classes that inherit from QObject or one of its subclasses (e.g., QWidget) can contain signals and slots. Signals are emitted by objects when they change their state in a way that may be interesting to other objects. This is all the object does to communicate. It does not know or care whether anything is receiving the signals it emits. This is true information encapsulation, and ensures that the object can be used as a software component. Signal Slots documentation

QObject::connect: Cannot queue arguments of type 'QModbusDevice::State'

I'm developing a multithreaded application, I need to instantiate n devices by modbus.
So I created a controller (ServiceSM) that instantiates N threads (ServiceSlots).
The devices are varied so I had to create "drivers" for each type of device, one of the drivers uses the QModbusClient class, so I created a controller to manage the device type.
schema
To test the operation of the state machine and connection to the device, I made an example code to run in a graphical interface.
I deleted some snippets of code that do not matter to make it easier to understand
In the MD4040driver class
When my code runs this section, the following messages appear.
If I instantiate the DeviceDriver class in the graphical interface, it works perfectly, the problem occurs when I instantiate it inside a thread.
when calls
modbusDevice->connectDevice()
MD4040drive::sm_conn() - try connect - this my message
Error:
QObject::connect: Cannot queue arguments of type
'QModbusDevice::State' (Make sure 'QModbusDevice::State' is registered
using qRegisterMetaType().)
QObject: Cannot create children for a parent that is in a different
thread. (Parent is QTcpSocket(0x24a6ce8), parent's thread is
ServiceSlots(0xea66488), current thread is QThread(0x2418a78)
QObject: Cannot create children for a parent that is in a different
thread. (Parent is QTcpSocket(0x24a6ce8), parent's thread is
ServiceSlots(0xea66488), current thread is QThread(0x2418a78)
void MD4040drive::sm_conn()
{
if (modbusDevice->state() != QModbusDevice::ConnectedState) {
modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, this->cfg.modbus.porta );
modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, this->cfg.modbus.ip);
modbusDevice->setTimeout( this->cfg.modbus.timeout );
modbusDevice->setNumberOfRetries(this->cfg.modbus.retries);
qDebug() << "MD4040drive::sm_conn() - try connect";
if (!modbusDevice->connectDevice()) {
qDebug() << "Erro: " << modbusDevice->errorString();
} else {
qDebug() << "Aguardando conexão...";
}
}
else{
//already connected...
this->getDados_NO_TH();
}
}
rest my code(parts)
devicedriverviewgui.h
devicedriverviewgui.cpp
class DeviceDriverViewGUI : public QDialog
{
Q_OBJECT
public:
explicit DeviceDriverViewGUI(QWidget *parent = 0);
~DeviceDriverViewGUI();
private slots:
void on_pbTry_clicked();
private:
Ui::DeviceDriverViewGUI *ui;
ServiceSlots *serviceSlot;
};
void DeviceDriverViewGUI::on_pbTry_clicked()
{
Equip equip_try = Equip();
serviceSlot = new ServiceSlots();
serviceSlot->setEquipamento(equip_try);
serviceSlot->start();
}
serviceslots.h
serviceslots.cpp
class ServiceSlots : public QThread
{
Q_OBJECT
public:
ServiceSlots();
void run();
private:
QTimer *timer;
DeviceDriver *device;
private slots:
void sm_getData();
void device_response(bool boEnd);
};
void ServiceSlots::run()
{
int e;
eventLoop = new QEventLoop();
timer = new QTimer();
connect(timer, SIGNAL(timeout()),this, SLOT(sm_controler()));
timer->start(TICK_SM_SLOT);
this->device = new DeviceDriver();
e = eventLoop->exec();
qDebug() << "Exit loop"<< e;
}
void ServiceSlots::sm_controler()
{
if(this->idleState){;}
else{
this->sm_getData();
this->idleState = true;
}
}
void ServiceSlots::sm_getData()
{
connect(device,SIGNAL(end(bool)),this,SLOT(device_response(bool)));
device->requestDeviceDriver(&this->equipamento,&this->next_date);
}
devicedriver.h
devicedriver.cpp
class DeviceDriver : public QObject
{
Q_OBJECT
public:
DeviceDriver();
void requestDeviceDriver(Equip *equip,QDateTime *date);
private:
//Drivers de dispositivos..
MD4040drive *md4040;
private slots:
//Request data to driver...
void request();
signals:
void end(bool boEnd);
};
void DeviceDriver::request()
{
connect(md4040,SIGNAL(end(bool)),this,SLOT(md4040_end(bool)));
this->md4040->requestMD4040drive(&this->equip,&this->date);
}
DeviceDriver::DeviceDriver(){
----
md4040 = new MD4040drive();
---
}
void DeviceDriver::requestDeviceDriver(Equip *equip, QDateTime *date){
this->equip = *equip;
this->date = *date;
this->request();
}
md4040drive.h
md4040drive.cpp
class MD4040drive : public QObject // QObject//public QObject QRunnable QThread
{
Q_OBJECT
public:
explicit MD4040drive(QObject *parent = 0);
~MD4040drive();
void requestMD4040drive(Equip *equip,QDateTime *date);
private:
void run();
QModbusClient *modbusDevice;
private slots:
void m_conn();
signals:
void end(bool boRun);
};
MD4040drive::MD4040drive(QObject *parent): QObject(parent)
{
modbusDevice = new QModbusTcpClient();
connect(modbusDevice, &QModbusClient::stateChanged,this, &MD4040drive::onStateChanged);
}
void MD4040drive::requestMD4040drive(Equip *equip, QDateTime *date)
{
this->equip = *equip;
this->date = *date;
this->run();
}
void MD4040drive::run()
{
this->sm_conn();
}
void MD4040drive::sm_conn()
{
if (modbusDevice->state() != QModbusDevice::ConnectedState) {
modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, this->cfg.modbus.porta );
modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, this->cfg.modbus.ip);
modbusDevice->setTimeout( this->cfg.modbus.timeout );
modbusDevice->setNumberOfRetries(this->cfg.modbus.retries);
qDebug() << "MD4040drive::sm_conn() - try connect";
if (!modbusDevice->connectDevice()) {
qDebug() << "Erro: " << modbusDevice->errorString();
} else {
qDebug() << "Aguardando conexão...";
}
}
else{
//already connected...
this->getDados_NO_TH();
}
}
There are a few problems:
You need to call qRegisterMetaType<QModbusDevice::State>() in main().
You need to maintain parent-child relationships between all objects that are potentially moved to other threads as a group.
The ServiceSlots and DeviceDriver classes seem to be unnecessary.
The ubiquitous this-> is non-idiomatic C++. Don't write this-> unless you need to disambiguate a member from a local variable.
Prefer to hold objects by value if they have the same lifetime as the parent object. Let the compiler generate memory management code for you!
Leverage C++11.
Fist of all, let's have a helper SafeThread class that provides us with a thread that is safely destructible at any time:
class SafeThread : public QThread {
Q_OBJECT
using QThread::run;
public:
using QThread::QThread;
~SafeThread() { quit(); wait(); }
};
The DeviceDriverViewGUI class can hold the drive and its thread by value:
class DeviceDriverViewGUI : public QDialog
{
Q_OBJECT
public:
explicit DeviceDriverViewGUI(QWidget *parent = nullptr);
private:
Ui::DeviceDriverViewGUI ui;
MD4040drive drive;
SafeThread driveThread{this};
Equip equipamento;
QDateTime nextDate;
Q_SLOT void on_pbTry_clicked();
};
Then, the pushbutton can be connected directly to the drive's thread context, and run the requestMD4040drive in the proper thread:
DeviceDriverViewGUI::DeviceDriverViewGUI(QWidget *parent) : QDialog(parent)
{
ui.setupUi(this);
// vvvvvv -- gives the thread context
connect(ui.pbTry, &QPushButton::clicked, &drive, [this]{
Q_ASSERT(QThread::currentThread() == drive.thread()); // ensured by the thread context
drive.requestMD4040drive(&equipamento, nextDate);
});
connect(&drive, &MD4040drive::end, this, [this](bool end){
//...
});
drive.moveToThread(&driveThread);
driveThread.start();
}
When done this way, you don't need any extraneous helper objects nor timers to queue requests. Qt handles all of it.
When passing Qt value classes to functions, pass them by const reference, not by pointer. The MD4040drive should look roughly as follows:
class MD4040drive : public QObject
{
Q_OBJECT
public:
explicit MD4040drive(QObject *parent = nullptr);
void requestMD4040drive(Equip *equip, const QDateTime &date);
Q_SIGNAL void end(bool boRun);
private:
Equip *equip = nullptr;
QDateTime date;
QModbusTcpClient modbusDevice{this};
Cfg cfg;
Q_SLOT void onStateChanged();
Q_SLOT void m_conn();
void sm_conn();
void getDados_NO_TH() {}
};
The implementation:
MD4040drive::MD4040drive(QObject *parent): QObject(parent)
{
connect(&modbusDevice, &QModbusClient::stateChanged,this, &MD4040drive::onStateChanged);
}
void MD4040drive::requestMD4040drive(Equip *equip, const QDateTime &date)
{
this->equip = equip;
this->date = date;
sm_conn();
}
void MD4040drive::sm_conn()
{
if (modbusDevice.state() != QModbusDevice::ConnectedState) {
modbusDevice.setConnectionParameter(QModbusDevice::NetworkPortParameter, cfg.modbus.porta );
modbusDevice.setConnectionParameter(QModbusDevice::NetworkAddressParameter, cfg.modbus.ip);
modbusDevice.setTimeout( this->cfg.modbus.timeout );
modbusDevice.setNumberOfRetries(this->cfg.modbus.retries);
qDebug() << "MD4040drive::sm_conn() - try connect";
if (!modbusDevice.connectDevice()) {
qDebug() << "Erro: " << modbusDevice.errorString();
} else {
qDebug() << "Aguardando conexão...";
}
}
else{
//already connected...
getDados_NO_TH();
}
}
The configuration class might look as follows - notice that the compiler will generate the necessary constructors and destructors for you:
struct Cfg {
struct Modbus {
int porta = 0;
QString ip = QStringLiteral("127.0.0.1");
int timeout = 1000;
int retries = 2;
} modbus;
};
Make sure 'QModbusDevice::State' is registered using qRegisterMetaType()
Means you need to call qRegisterMetaType<QModbusDevice::State>(); before connecting signal/slot that would pass this type of parameter between threads.
And/or add Q_DECLARE_METATYPE(QModbusDevice::State) macro at global scope (never understood clearly which one is actually needed, putting both works for sure...).
See this post for more details: Emitting signals with custom types does not work

How to test a QStateMachine?

I'm a bit confused about how to test a QStateMachine.
I have a project well organized with source code in one side and test code on the other side.
header
class Foo
{
signals:
void sigGoToStateOne();
void sigGoToStateTwo();
void sigGoToStateThree();
private:
QStateMachine *stateMachine;
QState *state1;
QState *state2;
void initStateMachine();
}
And in the source file
Foo::initStateMachine()
{
// constructors
state1->addTransition(this,SIGNAL(sigGoToStateTwo()),this->state2);
state2->addTransition(this,SIGNAL(sigGoToStateOne()),this->state1);
}
I would like to know if there is a beautiful way to test if my stateMachine is right. In other words, how my state machine reacts if I emit sigGoToStateThree() if I'm there, etc..
Solutions i see:
1 - Get the address of stateMachine (and eventually all other states) and test it (But i don't know how)
2 - Simulate signals (sigGoToStateX()) from a test file (Again, don't know if it's possible to emit signals of my class Foo in an other class)
My unique demand is I don't want to modify the core of my source file.
Thank's in advance.
In Qt 5, signals are always public methods. To make your code compatible with Qt 4, you can make the signals explicitly public like so:
class Foo {
public:
Q_SIGNAL void sigGoToStateOne();
...
}
Alternatively, you can keep arbitrary signal visibility, and declare a friend test class:
class Foo {
friend class FooTest;
...
}
Finally, you can create a test project where you use the Qt's test framework to test the Foo class's behavior. The code below works in both Qt 4 and Qt 5.
// main.cpp
#include <QCoreApplication>
#include <QStateMachine>
#include <QEventLoop>
#include <QtTest>
#include <QTimer>
class Waiter {
QTimer m_timer;
public:
Waiter() {}
Waiter(QObject * obj, const char * signal) {
m_timer.connect(obj, signal, SIGNAL(timeout()));
}
void stop() {
m_timer.stop();
QMetaObject::invokeMethod(&m_timer, "timeout");
}
void wait(int timeout = 5000) {
QEventLoop loop;
m_timer.start(timeout);
loop.connect(&m_timer, SIGNAL(timeout()), SLOT(quit()));
loop.exec();
}
};
class SignalWaiter : public QObject, public Waiter {
Q_OBJECT
int m_count;
Q_SLOT void triggered() {
++ m_count;
stop();
}
public:
SignalWaiter(QObject * obj, const char * signal) : m_count(0) {
connect(obj, signal, SLOT(triggered()), Qt::QueuedConnection);
}
int count() const { return m_count; }
};
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
typedef QSignalSpy SignalSpy;
#else
class SignalSpy : public QSignalSpy, public Waiter {
public:
SignalSpy(QObject * obj, const char * signal) :
QSignalSpy(obj, signal), Waiter(obj, signal) {}
};
#endif
class Foo : public QObject {
Q_OBJECT
friend class FooTest;
QStateMachine m_stateMachine;
QState m_state1;
QState m_state2;
Q_SIGNAL void sigGoToStateOne();
Q_SIGNAL void sigGoToStateTwo();
public:
explicit Foo(QObject * parent = 0) :
QObject(parent),
m_state1(&m_stateMachine),
m_state2(&m_stateMachine)
{
m_stateMachine.setInitialState(&m_state1);
m_state1.addTransition(this, SIGNAL(sigGoToStateTwo()), &m_state2);
m_state2.addTransition(this, SIGNAL(sigGoToStateOne()), &m_state1);
}
Q_SLOT void start() {
m_stateMachine.start();
}
};
class FooTest : public QObject {
Q_OBJECT
void call(QObject * obj, const char * method) {
QMetaObject::invokeMethod(obj, method, Qt::QueuedConnection);
}
Q_SLOT void test1() {
// Uses QSignalSpy
Foo foo;
SignalSpy state1(&foo.m_state1, SIGNAL(entered()));
SignalSpy state2(&foo.m_state2, SIGNAL(entered()));
call(&foo, "start");
state1.wait();
QCOMPARE(state1.count(), 1);
call(&foo, "sigGoToStateTwo");
state2.wait();
QCOMPARE(state2.count(), 1);
call(&foo, "sigGoToStateOne");
state1.wait();
QCOMPARE(state1.count(), 2);
}
Q_SLOT void test2() {
// Uses SignalWaiter
Foo foo;
SignalWaiter state1(&foo.m_state1, SIGNAL(entered()));
SignalWaiter state2(&foo.m_state2, SIGNAL(entered()));
foo.start();
state1.wait();
QCOMPARE(state1.count(), 1);
emit foo.sigGoToStateTwo();
state2.wait();
QCOMPARE(state2.count(), 1);
emit foo.sigGoToStateOne();
state1.wait();
QCOMPARE(state1.count(), 2);
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
FooTest test;
QTest::qExec(&test, a.arguments());
QMetaObject::invokeMethod(&a, "quit", Qt::QueuedConnection);
return a.exec();
}
#include "main.moc"
I am forcing all signal invocations to be done from the event loop, so that the event transitions will only happen while the event loop is running. This makes the test code uniformly wait after each transition. Otherwise, the second wait would time out:
Q_SLOT void test1() {
SignalSpy state1(&m_foo.m_state1, SIGNAL(entered()));
SignalSpy state2(&m_foo.m_state2, SIGNAL(entered()));
m_foo.start();
state1.wait();
QCOMPARE(state1.count(), 1);
emit m_foo.sigGoToStateTwo(); // The state2.entered() signal is emitted here.
state2.wait(); // But we wait for it here, and this wait will time out.
QCOMPARE(state2.count(), 1); // But of course the count will match.
emit m_foo.sigGoToStateOne();
state1.wait(); // This would timeout as well.
QCOMPARE(state1.count(), 2);
}
This can be worked around without the use of explicit queued calls by the use of a signal spy class that internally uses a queued connection.
Kuba Ober gives a very good analysis of how to use the test framework & SignalSpy to do in depth testing of your state machine.
If all you're trying to do is generate a sigGoToStateX() from a test file then don't forget that you can chain signals together.
So for example given a class "Tester":
class Tester : public QObject {
Q_OBJECT
public:
Tester(Foo *fooClass) {
//Connecting signals gives you the kind of behaviour you were asking about
connect(this, SIGNAL(testTransitionToState1()), fooClass, SIGNAL(sigGoToState1()));
connect(this, SIGNAL(testTransitionToState2()), fooClass, SIGNAL(sigGoToState2()));
connect(this, SIGNAL(testTransitionToState3()), fooClass, SIGNAL(sigGoToState3()));
}
void SwitchState(int newState) {
//Now any time we emit the test signals, the foo class's signals will be emitted too!
if (newState == 1) emit testTransitionToState1();
else if (newState == 2) emit testTransitionToState1();
else if (newState == 3) emit testTransitionToState1();
}
signals:
void testTransitionToState1();
void testTransitionToState2();
void testTransitionToState3();
}
So for example calling SwitchState(1) will invoke the correct signals for switching to state 1. If this simple case is all you need for testing then that's all you really need.
If you need something more complex, go with the full SignalSpy example.

QThread never runs/finishes before it can be used?

I have created a custom QObject class called EncodeThread, which looks as follows:
class EncodeThread : public QObject {
Q_OBJECT
public:
void set(SWSL::Video* v, QStringList f, QDir vDir);
void run();
public slots:
void encode();
signals:
void encodeProgress(int i);
private:
SWSL::Video* video;
QStringList files;
QDir videoDir;
};
As may be obvious, this class is used for encoding a video using an external library. Encode() contains the actual encoding routine, run() is a function I added while troubleshooting, though it's obviously non-functional:
void EncodeThread::run() {
if (currentThread() != this) {
// caller is in different thread.
QMetaObject::invokeMethod(this, "encode", Qt::QueuedConnection);
}
else {
encode();
}
}
The problem is when I use a QThread and the moveToThread() function on the EncodeThread instance, namely that nothing seems to happen. No data is written, and the instance never emits the signal which should save the encoded file to disk.
encThread.set(video, files, videoDir);
connect(&encThread, SIGNAL(encodeProgress(int)), cookVideoProgress, SLOT(setValue(int)));
connect(&encThread, SIGNAL(finished()), this, SLOT(videoCookEnd()));
connect(this, SIGNAL(videoEncode()), &encThread, SLOT(encode()));
encThread.moveToThread(&thread);
thread.start();
The above is how the whole setup is started. EncThread and thread variables are declared in the MainWindow class. I have made the set() function of EncodeThread call encode() after attempts to call encode() from the main thread using signals and QMetaObject failed.
I'm not new to threading, having used native Windows and Linux threads, as well as those of various cross-platform implementations, but QThreads really seem to baffle me. Any suggestions are more than welcome :)
Probably way to late to be any help to you, but here's a little demo program that puts an EncoderThread class to work. It probably doesn't quite mesh with your design (which your question only had fragments of), but it demonstrates running an object instance on its own thread and wiring 2 objects on different threads via signals/slots to let them communicate:
#include <stdio.h>
#include <QObject>
#include <QThread>
#include <QtCore/QCoreApplication>
// QSleeper is just a toy utility class that makes the
// protected QThread::sleep() family of functions
// publicly accessible. It's only use is for demo
// programs like this
class Sleeper : QThread
{
public:
static void sleep(unsigned long secs) { QThread::sleep(secs); }
static void msleep(unsigned long msecs) { QThread::msleep(msecs); }
static void usleep(unsigned long usecs) { QThread::usleep(usecs); }
};
// an Encoder class that maintains itself on is own thread
class EncodeThread : public QObject {
Q_OBJECT
public:
EncodeThread();
public slots:
void encode();
signals:
void encodeProgress(int i);
void finished();
private:
QThread myThread;
};
EncodeThread::EncodeThread() {
moveToThread(&myThread);
myThread.start();
}
void EncodeThread::encode()
{
printf("EncodeThread::encode() on thread %u\n", (unsigned int) QThread::currentThreadId());
for (int i = 0; i < 6; ++i) {
// encode for 1 second or so
printf("EncodeThread::encode() working on thread %u\n", (unsigned int) QThread::currentThreadId());
Sleeper::sleep(1);
emit encodeProgress(i);
}
emit finished();
printf("EncodeThread::encode() - done\n");
}
// a controller to manage and monitor an EncoderThread instance
class VideoEncoderController : public QObject
{
Q_OBJECT
public:
void start();
public slots:
void setValue(int);
void encodingDone();
signals:
void encodingBegin();
};
void VideoEncoderController::start()
{
printf("VideoEncoderController::start() on thread %u\n", (unsigned int) QThread::currentThreadId());
emit encodingBegin();
}
void VideoEncoderController::setValue(int x)
{
printf("VideoEncoderController::setValue(int %d) on thread %u\n", x, (unsigned int) QThread::currentThreadId());
}
void VideoEncoderController::encodingDone()
{
printf("VideoEncoderController::encodingDone() on thread %u\n", (unsigned int) QThread::currentThreadId());
}
// a demo program that wires up a VideoEncoderController object to
// an EncoderThread
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
EncodeThread encThread;
VideoEncoderController controller;
QObject::connect(&encThread, SIGNAL(encodeProgress(int)), &controller, SLOT(setValue(int)));
QObject::connect(&encThread, SIGNAL(finished()), &controller, SLOT(encodingDone()));
QObject::connect(&controller, SIGNAL(encodingBegin()), &encThread, SLOT(encode()));
printf("hello world on thread %u\n", (unsigned int) QThread::currentThreadId ());
controller.start();
return a.exec();
}
#include "main.moc"
You must derive QThread, not QObject. run() method is an abstract method of QThread.
For future programmers I'm adding this answer. There are generally 3 approaches to multi-threading in Qt. Low Level, Reusing(lets say Mid Level) and High Level.
Low level also includes two different approaches. In Low level you can Inherit QThread class and provide the code that you want to run in parallel inside void QThread::run() override;. After calling QThread::start() on your drived class, code in run will execute.
Another approach in Low Level Multi-threading in Qt is relaying on Qt's event loops. In this way you create a class that is drived from QObject, not necessarily from QThread and then move that class to a new QThread with this. And after that you will call start() on that QThread object(this QThread object is an object of type QThread . You don't have to subclass QThread here. Just instantiate one object. It is where I believe you misunderstood in your code).
After calling start() on your QThread object, its event loop starts in another thread and signals from anywhere in your code that are connected to your QObject drived class's slots will run in that event loop in parallel as long as you don't use Qt::DirectConnection as last argument to this.
These two approaches has their own benefits and drawbacks. When sub-classing QThread and using Qthread::run(), if your code inside run is running inside a while(true) loop, one of your processor's threads will always be occupied with Qthread::run() code and not in the threadpool for other threads in your program.
The QObject::moveToThread() approach is useful for most cases. But if the QObject drived classe's slot is going to be called very frequently, like 100 or 1000 times per second, memory usage rises, due to possible arguments passed to the slot, and some of this signals may never reach the slot.

Resources