I'm trying to use a thread to bring data from a MySQL database, but after a call to:
QThread* thread = new QThread;
Beacon *beacon = new Beacon(m_db, begin, end);
beacon->moveToThread(thread);
connect(beacon, &Beacon::values, this, &Tm::insertRow);
connect(beacon, &Beacon::finished, thread, &QThread::quit);
connect(beacon, &Beacon::finished, beacon, &Beacon::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
connect(thread, &QThread::started, beacon, &Beacon::executeQuerySmooth);
connect(beacon, &Beacon::finished, this, &Tm::finished);
thread->start();
I get:
QMYSQLResult::cleanup: unable to free statement handle
And the next time a try to use the connection I get:
QSqlError("2006", "QMYSQL: Unable to execute query", "MySQL server has gone away")
What might be happening?
You can only use the connection from a thread that created it (reference), or in general from one thread only.
There are two approaches you can take:
Maintain the database connection in a dedicated thread, and use it from there. The use of shortlived threads is a premature pessimization anyway, so it was a bad approach to begin with.
Move the database connection between worker threads from a thread pool. Use QtConcurrent::run to run a functor on a thread from the thread pool.
Note: Beacon::Beacon(...) constructor can't be using the database, it can only store a reference/pointer to it!
See this answer for more about postCall.
template <typename T>
void postCall(QThread * thread, T && functor) {
QObject source;
QObject::connect(&source, &QObject::destroyed,
QEventDispatcher::instance(thread), std::forward(functor));
}
Workers From The Thread Pool
class Tm : public QObject {
Q_OBJECT
QMutex m_dbMutex;
QSqlDatabase m_db;
Q_SIGNAL void openFailed();
Q_SIGNAL void openSucceeded();
public:
void openConnection() {
QMutexLocker lock(&m_dbMutex);
m_db.addDatabase("QSQLITE");
... // set database's properties
m_db.moveToThread(0); // we don't know what thread it will be used in
lock.unlock();
QtConcurrent::run([this]{
QMutexLocker lock(&m_dbMutex);
m_db.moveToThread(QThread::currentThread());
bool rc = m_db.open();
if (rc) {
m_db.moveToThread(0);
emit openSucceeded();
} else {
m_db.moveToThread(this->thread());
emit openFailed();
}
});
}
void beaconate() {
...
QSharedPointer<Beacon> beacon(new Beacon(&m_db, begin, end));
connect(beacon, &Beacon::values, this, &Tm::insertRow);
connect(beacon, &Beacon::finished, this, &Tm::finished);
beacon->setMoveToThread(0);
QtConcurrent::run([beacon]{
beacon->moveToThread(QThread::currentThread());
QMutexLocker lock(&m_dbMutex);
m_db.moveToThread(QThread::currentThread());
QEventLoop loop; // only if Beacon needs one
connect(beacon, &Beacon::finished, &loop, &QEventLoop::quit);
beacon->executeQuerySmooth();
loop.exec();
m_db.moveToThread(0);
});
}
~Tm() {
QMutexLocker lock(&m_dbMutex);
m_db.moveToThread(thread());
}
...
};
Dedicated Thread
class Thread : public QThread
{ using QThread::run; public: ~Thread() { quit(); wait(); } };
class Tm : public QObject {
Q_OBJECT
QSqlDatabase m_db;
Thread m_dbThread; // must be declared after m_db, so that it's destructed prior to m_db
Q_SIGNAL void openFailed();
Q_SIGNAL void openSucceeded();
public:
Tm(QObject * parent = 0) : QObject(parent) {
m_dbThread.start();
}
void openConnection() {
m_db.addDatabase("QSQLITE");
... // set database's properties
m_db.moveToThread(&m_dbThread);
postCall(&m_dbThread, [this]{
if (! m_db.open()) {
m_db.moveToThread(this->thread());
emit openFailed();
} else
emit openSucceeded();
});
}
void beaconate() {
...
auto beacon = new Beacon(&m_db, begin, end);
beacon.moveToThread(&m_dbThread);
connect(beacon, &Beacon::values, this, &Tm::insertRow);
connect(beacon, &Beacon::finished, beacon, &Beacon::deleteLater);
connect(beacon, &Beacon::finished, this, &Tm::finished);
postCall(&m_dbThread, [beacon]{ beacon->executeQuerySmooth(); });
}
~Tm() {
// Destructing objects living in other threads is undefined behavior
postCall(&m_dbThread, [this]{
if (m_db.thread() == QThread::currentThread())
m_db.moveToThread(thread());
});
}
...
};
Related
I have the following worker class:
class MediaWorker : public QObject
{
Q_OBJECT
public:
explicit MediaWorker(QObject *parent = 0);
~MediaWorker();
void Exit();
signals:
void Finished();
public slots:
void OnExecuteProcess();
};
In MediaWorker.cpp
void MediaWorker::Exit()
{
emit Finished();
}
void MediaWorker::OnExecuteProcess()
{
qDebug() << "Worker Thread: " << QThread::currentThreadId();
}
In my MainWindow I do the following:
this->threadMediaWorker = new QThread();
this->mediaWorker = new MediaWorker();
this->timerMediaWorker = new QTimer();
this->timerMediaWorker->setInterval(1000);
this->timerMediaWorker->moveToThread(this->threadMediaWorker);
this->mediaWorker->moveToThread(this->threadMediaWorker);
connect(this->threadMediaWorker, SIGNAL(started()), this->timerMediaWorker, SLOT(start()));
connect(this->timerMediaWorker, &QTimer::timeout, this->mediaWorker, &MediaWorker::OnExecuteProcess);
connect(this->mediaWorker, &MediaWorker::Finished, this->threadMediaWorker, &QThread::quit);
connect(this->mediaWorker, &MediaWorker::Finished, this->mediaWorker, &MediaWorker::deleteLater);
connect(this->threadMediaWorker, &QThread::finished, this->mediaWorker, &QThread::deleteLater);
this->threadMediaWorker->start();
The threading is working properly. When I close the application I terminate the thread in the destructor:
MainWindow::~MainWindow()
{
delete ui;
this->mediaWorker->Exit();
}
so Exit() emits the Finished signal which will hopefully delete the qthread and mediaworker class.
My question is if this is the proper way of terminating both the thread and media worker class?
My question is what is the proper way of terminating both the
worker thread and media worker class?
You can just ensure that the 'media' object gets deleted while the main window gets destructed by using either QScopedPointer or std::unique_ptr.
class MainWindow : public QMainWindow {
/// ...
QThread m_workerThread;
QScopedPointer<MediaWorker> m_pMediaObject;
/// ...
};
void MainWindows::init()
{
// ... other initialization skipped ...
// for dynamic allocation of the object and keeping the track of it
m_mediaObject.reset(new MediaWorker());
m_workerThread.moveToThread(m_mediaObject.data());
}
void MainWindow::stopWorker()
{
if (m_workerThread.isRunning())
{
m_workerThread.quit(); // commands Qt thread to quit its loop
// wait till the thread actually quits
m_workerThread.wait(); // possible to specify milliseconds but
// whether or not to limit the wait is
// another question
}
}
If the worker thread used for updating the UI it makes sense to attempt to stop the worker thread before the UI objects released in
void MainWindow::closeEvent(QCloseEvent *)
{
stopWorker();
}
But there is a chance that the main window never gets closeEvent() called before the destruction so we should handle that:
MainWindow::~MainWindow()
{
stopWorker();
// it also destroys m_mediaObject
}
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
I have the following code that works as expected. It updates the progress bar value in each for loop. The only issue I have is when I'm done and call emit ProcessUserRowsFinished() in the method OnProcessUserRowsStarted the program crashes.
class UsersProcess: public QObject
{
Q_OBJECT
public:
UsersProcess(CTCore::DBConnect* db_context, UserSettingsMap user_settings_map);
void SetProgressBar(QProgressBar *progress_bar);
private:
QProgressBar *progressBar;
QSharedPointer<QList<CTCoreZen::User>> listUsers;
QScopedPointer<QThread> threadRowWorker;
signals:
void ProcessUserRowsFinished();
void ProgressBarSetValue(int value);
}
void UsersProcess::SetProgressBar(QProgressBar *progress_bar)
{
this->progressBar = progress_bar;
}
void UsersProcess::OnUserListSuccess(QList<CTCoreZen::User> *users)
{
this->listUsers.reset(users);
this->progressBar->setVisible(true);
this->progressBar->setTextVisible(true);
this->progressBar->setMinimum(0);
this->progressBar->setMaximum(this->listUsers->size());
this->progressBar->setValue(0);
this->threadRowWorker.reset(new QThread());
this->moveToThread(this->threadRowWorker.data());
connect(this->threadRowWorker.data(), &QThread::started, this, &UsersProcess::OnProcessUserRowsStarted);
connect(this, &UsersProcess::ProgressBarSetValue, this->progressBar, &QProgressBar::setValue);
connect(this, &UsersProcess::ProcessUserRowsFinished, this->threadRowWorker.data(), &QThread::quit);
connect(this, &UsersProcess::ProcessUserRowsFinished, this, &UsersProcess::deleteLater);
connect(this->threadRowWorker.data(), &QThread::finished, this->threadRowWorker.data(), &QThread::deleteLater);
this->threadRowWorker->start();
}
void UsersProcess::OnProcessUserRowsStarted()
{
int row = 0;
UsersData userData(this->dbContext);
int maxRows = this->listUsers->size();
for(auto iter = this->listUsers->begin(); iter != this->listUsers->end(); ++iter)
{
row++;
emit this->ProgressBarSetValue(row);
}
emit ProcessUserRowsFinished();
}
This is because of your thread has been deleted.
This call deletes the instance of class UsersProcess and QScopedPointer deletes the thread.
connect(this, &UsersProcess::ProcessUserRowsFinished, this, &UsersProcess::deleteLater);
But you also have this connections
connect(this, &UsersProcess::ProcessUserRowsFinished, this->threadRowWorker.data(), &QThread::quit);
connect(this->threadRowWorker.data(), &QThread::finished, this->threadRowWorker.data(), &QThread::deleteLater);
When one of this events fires the thread is already deleted.
I got this Error Message when I compile my project:
"Can not convert 'Principal::setValues' from type 'void*(Principal::)(void*)' to type 'void*()(void)' "
...
enter code here
void* Principal:: setValues(void*){
QString velocidadCARGA=QString::number(VelocidadCargador);
QString velocidadLAVA=QString::number(VelocidadLavado);
ui->Velocidad_Carga->setText(velocidadCARGA);
ui->Velocidad_Lavado->setText(velocidadLAVA);
ui->lbl_CantidadActual_Banda_Principal->setNum(botellasCargadas);
return NULL;
}
void Principal::on_Start_Cargador_clicked(){
pthread_t hilo3;
pthread_create(&hilo3,NULL,setValues,NULL);//error in this line.
pthread_join(hilo3,NULL);
}
Principal::setValues is a member function, so its type does not conform with a function type required by pthread_create.
To launch a member function in a thread you can declare some static method and pass this object into it:
class Principal
{
...
static void* setValuesThread(void *data);
...
}
void* Principal::setValuesThread(void *data)
{
Principal *self = reinterpret_cast<Principal*>(data);
self->setValues();
return NULL;
}
// your code
void Principal::setValues()
{
QString velocidadCARGA=QString::number(VelocidadCargador);
QString velocidadLAVA=QString::number(VelocidadLavado);
ui->Velocidad_Carga->setText(velocidadCARGA);
ui->Velocidad_Lavado->setText(velocidadLAVA);
ui->lbl_CantidadActual_Banda_Principal->setNum(botellasCargadas);
}
void Principal::on_Start_Cargador_clicked()
{
pthread_t hilo3;
pthread_create(&hilo3, NULL, Principal::setValuesThread, this);
pthread_join(hilo3,NULL);
}
But if Principal is a Qt widget (and I suppose it is), this code won't work because in Qt you can access widgets from the main thread only.
If you want to have some heavy calculations in a worker thread and then pass the results to your widget, you can use QThread and Qt signals/slots mechanism.
A simple example:
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread(QObject *parent = 0);
void run();
signals:
void dataReady(QString data);
}
void MyThread::run()
{
QString data = "Some data calculated in this worker thread";
emit dataReady(data);
}
class Principal
{
...
public slots:
void setData(QString data);
}
void Principal::setData(QString data)
{
ui->someLabel->setText(data);
}
void Principal::on_Start_Cargador_clicked()
{
MyThread *thread = new MyThread;
connect(thread, SIGNAL(dataReady(QString)), this, SLOT(setData(QString()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
Here is some related articles on Qt multithreading technologies:
http://doc.qt.io/qt-5/thread-basics.html
http://doc.qt.io/qt-5/threads-technologies.html
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.