The event loop processes all events that are available: your expectation is incorrect. How can the event loop divine that you would like to "suspend" some event? It can't.
What you really want - but you wrote it backwards - is for the network management thread to start once the I/O manager is done initializing. That way nothing has to wait for anything!
Waiting for things happens when you write pseudosynchronous code, and that's the source of all your woes. Do not spin event loops manually. Do not inherit from QThread
(other than to make it RAII).
Finally, it'd be nice if neither the io_manager
nor network_manager
needed special code to handle the fact that they are in their own threads.
First, we need to include functor dispatch code from this answer.
// https://github.com/KubaO/stackoverflown/tree/master/questions/thread-sync-50188307
#include <QtCore>
namespace detail { template <typename F> struct FEvent : QEvent {
const QObject *const obj;
const QMetaObject *const type = obj->metaObject();
typename std::decay<F>::type fun;
template <typename Fun>
FEvent(const QObject *obj, Fun &&fun) :
QEvent(QEvent::None), obj(obj), fun(std::forward<Fun>(fun)) {}
~FEvent() { // ensure that the object is not being destructed
if (obj->metaObject()->inherits(type)) fun();
}
}; }
template <typename F> static void post(QObject *obj, F &&fun) {
Q_ASSERT(!qobject_cast<QThread*>(obj));
QCoreApplication::postEvent(obj, new detail::FEvent<F>(obj, std::forward<F>(fun)));
}
Then, we need a true RAII thread that's always safe to destruct:
class Thread final : public QThread {
Q_OBJECT
void run() override {
if (!eventDispatcher())
QThread::run();
}
public:
using QThread::QThread;
using QThread::exec;
~Thread() override {
requestInterruption();
quit();
wait();
}
template <typename F> void on_start(F &&fun) {
connect(this, &QThread::started, std::forward<F>(fun));
}
};
The started
signal is emitted within the thread, and any functors passed to on_start
effectively become injected into the thread. The run()
method is reimplemented to only start an event loop if one wasn't previously running. This way we can inject entire thread bodies that construct objects and spin the event loop.
The I/O and network manager are simple objects, not threads. We defer the construction of the io_manager
and network_manager
to their respective threads. That way, the individual objects don't have to be aware of what thread they run on.
#include <memory>
using app_logger = QObject;
using room_logger_manager = QObject;
class io_manager : public QObject {
Q_OBJECT
app_logger app_log{this};
room_logger_manager room_log_mgr{this};
public:
using QObject::QObject;
};
class network_manager : public QObject {
Q_OBJECT
public:
network_manager(QObject *parent = {}) : QObject(parent) {
do_something1();
do_something2();
}
void do_something1() { qDebug() << __FUNCTION__; }
void do_something2() { qDebug() << __FUNCTION__; qApp->quit(); }
};
int main(int argc, char *argv[]) {
QCoreApplication app{argc, argv};
QPointer<io_manager> iomgr; //optional
QPointer<network_manager> netmgr; //optional
Thread io_thread, network_thread;
io_thread.on_start([&]{
qDebug() << "I/O thread is running.";
io_manager mgr;
iomgr = &mgr;
network_thread.start();
io_thread.exec();
});
network_thread.on_start([&]{
qDebug() << "Network thread is running.";
network_manager mgr;
netmgr = &mgr;
network_thread.exec();
});
io_thread.start();
return app.exec(); // RAII all the way!
}
#include "main.moc"
Output:
I/O thread is running.
Network thread is running.
do_something1
do_something2
Notice how the lifetime of all objects is handled automatically.