0

I have problem with waiting in 'network' thread for a signal from 'io' thread. In io_thread.cpp I have:

void io_thread::run()
{
  app_log = new app_logger();
  room_log_mgr = new room_logger_manager(this);

  emit init_finished();
  exec();
  QCoreApplication::processEvents();

  delete room_log_mgr;
  delete app_log;
}

In 'main' thread I have two queued events (do_something1 and do_something2 executes in 'network' thread):

QMetaObject::invokeMethod(network_obj, "do_something1", Qt::QueuedConnection);
QMetaObject::invokeMethod(network_obj, "do_something2", Qt::QueuedConnection);

In do_something1 method I have this code:

QEventLoop loop;
QObject::connect(&logger_thread, &io_thread::init_finished, &loop, &QEventLoop::quit, Qt::BlockingQueuedConnection);
logger_thread.start();
loop.exec();

So I waiting for init_finished signal from io_thread and loop.exec() should block 'network' thread from executing. But when program execute loop.exec() then immediately switch to executing do_something2, and after do_something2 has finished program execution backs to do_something1 after line loop.exec().

It's looks like local event loop processing more events that I have expected.

I want to wait for a &io_thread::init_finished signal without processing other events in local event loop. How can I do that?

exfoo
  • 35
  • 5

1 Answers1

0

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.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313