Using a QEventLoop to block execution in a Qt program, how can you connect 10 individual signals to the one loop in such a way that it won't unblock until all 10 signals are received?
Please avoid using nested loops when possible. But if you are absolutely sure that you have no way around, you need to have a way to store which of the signals have fired and which of them haven't, and quit the event loop (i.e. maybe by emitting a signal connected to your event loop's QEventLoop::quit) only when all signals have fired.
Here is a minimum example that uses 10 QTimers with different intervals, and waits for all of them to fire before quitting a nested event loop:
#include <QtCore>
#include <algorithm>
int main(int argc, char* argv[]) {
QCoreApplication a(argc, argv);
const int n = 10;
//10 timers to emit timeout signals on different intervals
QTimer timers[n];
//an array that stores whether each timer has fired or not
bool timerFired[n]= {};
QEventLoop loop;
//setup and connect timer signals
for(int i=0; i<n; i++) {
timers[i].setSingleShot(true);
QObject::connect(&timers[i], &QTimer::timeout, [i, &timerFired, &loop]{
qDebug() << "timer " << i << " fired";
timerFired[i]=true;
//if all timers have fired
if(std::all_of(std::begin(timerFired), std::end(timerFired),
[](bool b){ return b; }))
loop.quit(); //quit event loop
});
timers[i].start(i*i*100);
}
qDebug() << "executing loop";
loop.exec();
qDebug() << "loop finished";
QTimer::singleShot(0, &a, &QCoreApplication::quit);
return a.exec();
}
Related
Struggling emitting a signal from main/QML thread to another thread with a QList< QStringList > parameter. Variations I'v tried:
Q_DECLARATIVE_METATYPE in and out
Using EventExport vs. const EventExport& in signal and slot profiles
Sending empty EventExport in prepareExport() so emit has no/low data amount
Checking connect statement (always returns true)
Having qDebug() in prepareExport() and signal always appears to be emitted
Calling emit right after connect as a test (Works! Think you're going to tell me the main thread or cryoUtility objects don't exist but they do!)
Tried qRegisterMetaType with () and ("EventExport")...some say use text for typedef types
Any thoughts much appreciated!
sqlquery_model.h (not certain I need Q_DECLARATIVE_METATYPE but tried with and without...no change)
typedef QList<QStringList> EventExport;
Q_DECLARE_METATYPE(EventExport);
Q_INVOKABLE void prepareExport();
signals:
void updateEventListDataSig(const EventExport&);
sqlquery_model.cpp (this is connected to a qml page using TableView model...this emit does not seem to work)
void SqlQueryModel::prepareExport() {
if (this->rowCount() > 0) {
EventExport eventsList;
for(int i=0; i<this->rowCount(); ++i) {
QStringList eventInfo;
eventInfo.append(this->record().value(0).toString());
eventInfo.append(this->record().value(1).toString());
eventInfo.append(this->record().value(2).toString());
eventInfo.append(this->record().value(3).toString());
eventInfo.append(this->record().value(4).toString());
eventsList.append(eventInfo);
}
emit updateEventListDataSig(eventsList);
qDebug() << "Emit updatedEventListData" << eventsList.count();
}
}
main.cpp (includes sqlquery_model.h, need this as cryoUtility is a separate thread using Qt::QueuedConnection)
// Use string if using typedef method
qRegisterMetaType<EventExport>("EventExport");
mediator.h
void updateEventListDataSig(const EventExport&);
mediator.cpp (connects mainly live here, this test event works)
bool ret = connect(this, SIGNAL(updateEventListDataSig(const EventExport&)), cryoUtility, SLOT(updateEventListData(const EventExport&)), Qt::QueuedConnection);
EventExport ed;
emit updateEventListDataSig(ed);
qDebug() << "Event list CONN: " << ret;
utilities.h
void updateEventListData(const EventExport&);
utilities.cpp (this is the slot, trigger once on test call)
void Utilities::updateEventListData(const EventExport& el) {
qDebug() << "Load event list: ";// << el.count();
//eventList = el;
}
So, after more study, sqlmodelquery connection would have to occur in its constructor as it isn't active yet until its QML page loads.
I'm trying to execute a network request and wait for the response before moving on. Whereas using a QEventLoop works to wait for the request, the problem is if there are other signals fired in other parts of the program they will execute first.
Here is an example of the code re-producing the problem. You can see the network request (I'm using http://example.com here just for example) is clearly going to be issued before the second singleShot timer will fire.
#include <QApplication>
#include <QTimer>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
void longFunction()
{
qDebug() << "Starting Long Function";
QEventLoop localEventLoop;
QNetworkAccessManager manager;
QNetworkReply *reply = manager.get(QNetworkRequest(QUrl("http://example.com")));
QObject::connect(reply, &QNetworkReply::finished, &localEventLoop, &QEventLoop::quit);
// I wish to block here
// - do not want to pass this point and run
// other events if fired from the main event loop
localEventLoop.exec();
qDebug() << "Finishing Long Function";
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTimer::singleShot(0, &longFunction);
QTimer::singleShot(1, []() {
qDebug() << " -- Some other function that was issued after";
qDebug() << " -- 'longFunction' started but before 'longFunction' ended";
});
return a.exec();
}
Here is the output:
Here is the desired output:
Edit
To add some more info. The code above is obviously an over simplification of the problem as I can't easily post all of the code. Basically whats happening is there is code somewhere else in my project that will emit a signal that is connected to the longFunction() above. Obviously I want to wait for a response from the REST call before continuing because the response will dictate the elements later in that function. The issue is that at some point later (potentially before the REST call finishes), some other parts of my code might trigger some other signal and I don't want those other signals to be processed. I just want to wait for the ONE connection on the localEventLoop to be processed.
Any help would be greatly appreciated
QTimer::singleShot(0, &longFunction);
is an asynchronous function. It returns before void longFunction() executed the QEventLoop. The result is that your second QTimer::singleShot(1, []() { will trigger immediately. The solution is to move QEventLoop outside your function, before QTimer::singleShot(1, []() {
I've wrote this code looking at examples online of how I'm supposed to run a console program that doesn't just run and quit and one that does. Based on a Qt console application. This one here, I wanted it to quit. I've understood pretty much everthing excepth the QTimer::singleShot line. If the line is commented out, the application will run but will not quit. If it is left, the application will run and quit as expected. Can anyone explain to me why?
dostuff.h
#ifndef DOSTUFF_H
#define DOSTUFF_H
#include <QObject>
#include <iostream>
class DoStuff: public QObject
{
Q_OBJECT
public :
DoStuff(QObject *parent = 0);
public slots:
void run();
signals:
void finished();
};
#endif // DOSTUFF_H
And the implementation dostuff.cpp
#include "dostuff.h"
DoStuff::DoStuff(QObject *parent):QObject(parent)
{
}
void DoStuff::run(){
for (int i = 0; i < 10000; i++){
std::cout << "Processing " << i << std::endl;
}
emit(finished());
}
My main.cpp
#include <QCoreApplication>
#include <QTimer>
#include "dostuff.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
DoStuff *dostuff = new DoStuff(&a);
QObject::connect(dostuff,SIGNAL(finished()),&a,SLOT(quit()));
dostuff->run();
// WHY THIS??
QTimer::singleShot(10,dostuff,SLOT(run()));
return a.exec();
}
QTimer is not required to exit properly; You just need to provide a way to get your application to break the event loop at some point. In GUI application, Qt does that automatically when the last window is closed.
In Console applications, you can:
Either run your application without an event loop (if you have a straight-forward simple control flow in your application).
Or (if you require an event loop to handle some events or cross thread signal/slots) you need to have some event that makes your application break the event loop and quit. This event should only be triggered when the application has finished its job.
The code sample you have in your question is really simple, and does not require an event loop to run properly. The only effect the QTimer has in your code is that it delays execution for 10 ms. Here is the same code sample without running an event loop:
#include <QtCore>
class DoStuff: public QObject
{
Q_OBJECT
public :
DoStuff(QObject *parent = 0) : QObject(parent) {}
public slots:
void run() {
for (int i = 0; i < 10000; i++){
qInfo() << "Processing " << i;
}
emit finished();
}
signals:
void finished();
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
DoStuff dostuff;
QObject::connect(&dostuff, &DoStuff::finished,
&a, &QCoreApplication::quit);
dostuff.run();
return 0; //no event loop required
}
#include "main.moc"
If you start an event loop, you may notice that the quit slot does not work when not using QTimer::singleShot. The reason for this is that quit is called before the event loop is even started (and the call has no effect at all). That's why according to the docs, it is recommended to connect to quit using a queued connection:
It's good practice to always connect signals to this slot using a QueuedConnection. If a signal connected (non-queued) to this slot is emitted before control enters the main event loop (such as before "int main" calls exec()), the slot has no effect and the application never exits. Using a queued connection ensures that the slot will not be invoked until after control enters the main event loop.
So, if you want to have an event loop in your code above, you just need to connect using a Qt::QueuedConnection:
#include <QtCore>
class DoStuff: public QObject
{
Q_OBJECT
public :
DoStuff(QObject *parent = 0) : QObject(parent) {}
public slots:
void run() {
for (int i = 0; i < 10000; i++){
qInfo() << "Processing " << i;
}
emit finished();
}
signals:
void finished();
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
DoStuff dostuff;
QObject::connect(&dostuff, &DoStuff::finished,
&a, &QCoreApplication::quit,
Qt::QueuedConnection);
// ^^^^^^^^^^^^^^^^
// use a queued connection
dostuff.run();
return a.exec(); //start an event loop
}
#include "main.moc"
Timer is needed to postpone execution. Since you want to have a running event loop, a.exec() has to be called, then the timer executes your code. When your code finishes running, it triggers finished signal, that is tied to QCoreApplication::quit - that's the needed exit for event loop running inside a.exec().
Btw, you have to remove: dostuff->run(); from your code.
I have a strange behavior with Lambda and timer on Qt 5.7.1. Probably a mistake from me.
I start a connection with a socket and set a timer to check whether it was connected or not after a certain amount of time.
The signal connected of the socket will stop the time.
However, with the following implementation, the timer does not stop even if the connected signal is called.
constructor:
m_connectTimeout.setInterval(5000);
connect(&m_socket, &QLocalSocket::connected, [&]()
{
// this is called first and should stop the timer.
m_connectTimeout.stop();
});
connect(&m_connectTimeout, &QTimer::timeout, [&](){
// this is still called
});
Here is a minimum example with problem reproducible on Qt5.7.1 and Windows 10.
#include <QtCore>
#include <QtNetwork>
#define PIPENAME "testbug"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTimer timer;
QLocalSocket socketClient, *socketServer;
QLocalServer server;
timer.setInterval(2000);
QObject::connect(&timer, &QTimer::timeout, [&]
{
qDebug() << "client connection timed out";
timer.stop();
});
QObject::connect(&socketClient, &QLocalSocket::connected, [&]
{
qDebug() << "client connected";
timer.stop();
});
QObject::connect(&server, &QLocalServer::newConnection, [&]
{
qDebug() << "server got connection";
socketServer = server.nextPendingConnection();
});
server.setSocketOptions(QLocalServer::WorldAccessOption);
server.listen(PIPENAME);
qDebug() << "client connecting. . .";
socketClient.connectToServer(PIPENAME, QLocalSocket::ReadWrite);
timer.start();
return a.exec();
}
Output of the program:
client connecting. . .
client connected
server got connection
client connection timed out
I also noticed it's not always reproducible and seems somehow random.
Actually it seems the code works, it's just that the connection is so fast, that the timer.stop is called before the timer.start.
Starting the timer before calling connect to server seems to solve the issue
m_timer.start();
m_socketClient.connectToServer(PIPENAME, QLocalSocket::ReadWrite);
This would mean that connectToServer does some calls on the event loop in the background, allowing the slots to be called, even before the next line is executed.
I have a small example using Slots called by mousemovement and mousewheel.
Now i have the problem that when i zoom and move at the same time, first the onZoom-slot is called and before it is finished it is calling the onMouseMoved-slot. That causes the first slot to lock the mutex (in my original program used by another thread) and the second one to wait for it.
How can I prevent the slots to interrupt each other (and why are they doing it in first place since they are in same thread?).
I read something about using Qt::QueuedConnection but that causes an access violation exception.
main.cpp
#include "ppi.h"
#include <QtGui/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
PPI w;
w.show();
return a.exec();
}
ppi.h
#ifndef PPI_H
#define PPI_H
#include <QtGui/QMainWindow>
#include <QGraphicsView>
#include <QDebug>
#include <QWheelEvent>
#include <QgraphicsEllipseItem>
#include <QMouseEvent>
#include <QMutex>
#include <QThread>
#include <QGraphicsSceneMouseEvent>
//#include "ui_ppi.h"
class PPIView : public QGraphicsView
{
Q_OBJECT
public:
PPIView(QWidget * parent = 0)
: QGraphicsView(parent)
{};
~PPIView(){};
private slots:
void wheelEvent(QWheelEvent *event)
{emit zoom(event);};
signals:
void zoom(QWheelEvent *event);
};
class PPIScene : public QGraphicsScene
{
Q_OBJECT
public:
PPIScene(QObject *parent)
: QGraphicsScene(parent)
{};
~PPIScene(){};
private:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{emit mouseMoved(event);};
signals:
void mouseMoved(QGraphicsSceneMouseEvent *event);
};
class PPI : public QMainWindow
{
Q_OBJECT
public:
PPI(QWidget *parent = 0, Qt::WFlags flags = 0)
: QMainWindow(parent, flags)
{
//ui.setupUi(this);
//ppiScene is inherited from QGraphicsScene, overriding mouseMoveEvent so it emits mouseMoved();
ppiScene = new PPIScene(this);
gVPPI = new PPIView(this);
gVPPI->setMinimumSize(1024,1024);
gVPPI->show();
test = new QGraphicsEllipseItem(-10, -10, 20, 20);
ppiScene->addItem(test);
gVPPI->adjustSize();
connect(ppiScene, SIGNAL(mouseMoved(QGraphicsSceneMouseEvent*)), this, SLOT(onMouseMoved(QGraphicsSceneMouseEvent*)));
connect(gVPPI, SIGNAL(zoom(QWheelEvent*)), this, SLOT(onZoom(QWheelEvent*)));
//ui.gVPPI is inherited from QGraphicsView, overriding wheelEvent, so it emits zoom()
gVPPI->setScene(ppiScene);
gVPPI->setMouseTracking(true);
};
~PPI(){};
QMutex mutex;
private:
//Ui::ppiClass ui;
PPIScene* ppiScene;
PPIView *gVPPI;
QGraphicsEllipseItem *test;
protected slots:
void onZoom(QWheelEvent *event)
{
qDebug() << "Zoom lock" << QThread::currentThreadId();
mutex.lock();
qDebug() << "Zoom locked";
if(event->delta() > 0)
gVPPI->scale(1.01, 1.01);
else
gVPPI->scale(1/1.01, 1/1.01);
qDebug() << "Zoom unlock";
mutex.unlock();
qDebug() << "Zoom unlocked";
};
void onMouseMoved(QGraphicsSceneMouseEvent *event)
{
qDebug() << "Move lock" << QThread::currentThreadId();
mutex.lock();
qDebug() << "move locked";
test->setPos(test->pos()+event->scenePos()-event->lastScenePos());
qDebug() << "Move unlock";
mutex.unlock();
qDebug() << "Move unlocked";
};
};
#endif // PPI_H
Output qDebug():
Move lock 0x1514
move locked
Move unlock
Move unlocked
Move lock 0x1514
move locked
Move unlock
Move unlocked
Zoom lock 0x1514
Zoom locked
Move lock 0x1514
You can use QObject::blockSignals to block signals for specific object,
but I think you have wrong idea about how your code works.
There is one GUI thread, and if signals + slots in the same GUI thread,
as in your case, then they called in sequence order. And if PPI::onZoom will called in another thread context then you have problem here, because of usage of things like ui.gVPPI->scale in non GUI thread is not allowed and may cause crash on assert, or just crash, or randomly works, UB as it is.
The situation when you see that slot 1 was called, then before finish of slot 1 slot 2 is called is likely because of you call some function inside slot 1 that call another function etc and some function deep inside call signal 2, which emit call of slot 2, just set breakpoints in your debugger and findout what is going on.
It doesn't make sense to use any form of locking in a single GUI thread that doesn't cooperate with other threads. Everything in the GUI thread is already serialized for you. You're fighting an imaginary problem. As long as your code is executing, nothing else will execute behind your back in the same thread.
onZoom-slot is called and before it is finished it is calling the onMouseMoved-slot
This is false. Nothing of the sort happens in the code you show. Perhaps ui.gVPPI->scale() is reentering the event loop. You really need to show a self-contained example: your problem is in the code you don't show.
How can I prevent the slots to interrupt each other
It's already prevented for you. You need to do nothing.
in my original program used by another thread
Generally speaking, objects living in the GUI thread cannot have methods directly invoked from other threads.
Most of the time, throwing a second thread at a problem makes you end up with two problems. Properly designed non-blocking code will work correctly, if perhaps a bit slow, even when run in a single thread. That'd be your point of departure - you then move objects to other threads simply to spread the work around. Ideally, though, async processing should be done by running work asynchronously through QtConcurrent::run.
I managed to set up my debugger and the problem seems QGraphicsView::scale() is calling QgraphicsScene::mouseMoveEvent() deep inside directly. So I need to introduce a variable which tells the mouseMoveEvent, whether it was called from QGraphicsView::scale() or from physical mouse movement.
protected slots:
void onZoom(QWheelEvent *event)
{
qDebug() << "Zoom lock" << QThread::currentThreadId();
mutex.lock();
qDebug() << "Zoom locked";
scale = true;
if(event->delta() > 0)
gVPPI->scale(1.01, 1.01);
else
gVPPI->scale(1/1.01, 1/1.01);
scale = false;
qDebug() << "Zoom unlock";
mutex.unlock();
qDebug() << "Zoom unlocked";
};
void onMouseMoved(QGraphicsSceneMouseEvent *event)
{
if(scale == false)
{
qDebug() << "Move lock" << QThread::currentThreadId();
mutex.lock();
qDebug() << "move locked";
}
test->setPos(test->pos()+event->scenePos()-event->lastScenePos());
if(scale == false)
{
qDebug() << "Move unlock";
mutex.unlock();
qDebug() << "Move unlocked";
}
};