Oleg Lokshyn
Oleg Lokshyn

Reputation: 647

Qt 5: emit signal from non-Qt thread

What I am trying to achieve is a cross-platform TCP socket library built on top of Qt QTcpServer/Socket. I faced an issue that signals, emitted from a non-Qt thread without Qt event loop, are not received by objects in QThread with the event loop.

I have found that emitting from a non-Qt thread worked before with Qt::QueuedConnection connection type set explicitly, according to this and this questions. Those questions are rather old and relate to Qt 4. So I wonder if this functionality is still supported in Qt 5.

I have explored the Qt 5 source code and found:

  1. Emitting a signal is just a call to QMetaObject::activate
  2. QMetaObject::activate, in turn, calls queued_activate, if connection type is set to Qt::QueuedConnection or the current thread (emitter thread) is different from the thread receiver lives in (in my case, Qt::QueuedConnection is set explicitly).
  3. queued_activate creates an event object and calls QCoreApplication::postEvent
  4. QCoreApplication::postEvent does proper locking and puts the event into the receiver event queue. Despite postEvent is a static QCoreApplication function that uses self - a pointer to current static QCoreApplication singleton, it should work properly even if there is no global QCoreApplication object (i.e. self == 0).

Given this, I suppose that for signal&slot mechanism to work properly, only the receiver thread has to have the Qt event loop that will dispatch the event from the queue, correct me if I am wrong.

Despite that, emitting a signal from a non-Qt thread does not work for me. I have created as simple demo app as possible that demonstrates the malfunctioning of the signal&slot.

MyThread component just inherits QThread and moves inside itself (moveToThread) QObject-derived ThreadWorker.

MyThread.h:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

#include "ThreadWorker.h"

class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();

signals:
    void mySignal();

private:
    ThreadWorker m_worker;
};

#endif // MYTHREAD_H

MyThread.cpp:

#include "MyThread.h"

#include "ThreadWorker.h"

MyThread::MyThread()
    : m_worker(*this)
{
    m_worker.moveToThread(this);
}

Thread worker is needed to live in MyThread thread and to connect to MyThread`s mySignal() signal.

ThreadWorker.h:

#ifndef THREADWORKER_H
#define THREADWORKER_H

#include <QObject>

class MyThread;

class ThreadWorker : public QObject
{
    Q_OBJECT
public:
    explicit ThreadWorker(const MyThread& thread);

public slots:
    void mySlot();
};

#endif // THREADWORKER_H

ThreadWorker.cpp:

#include "ThreadWorker.h"

#include <QDebug>

#include "MyThread.h"

ThreadWorker::ThreadWorker(const MyThread& thread)
    : QObject(0)
{
    connect(&thread, SIGNAL(mySignal()),
            this, SLOT(mySlot()),
            Qt::QueuedConnection);
}

void ThreadWorker::mySlot()
{
    qDebug() << "mySlot called! It works!";
}

Finally, the main.cpp:

#include <QCoreApplication>
#include <QDebug>

#include "MyThread.h"

int main(int argc, char *argv[])
{
//    QCoreApplication a(argc, argv);

    MyThread my_thread;
    my_thread.start();

    emit my_thread.mySignal();
    qDebug() << "mySignal emitted";

    my_thread.wait();

//    return a.exec();
}

Note, that if I uncomment QCoreApplication creation, I get the correct output:

mySignal emitted
mySlot called! It works!

but if I leave it as is, I get only

mySignal emitted
QEventLoop: Cannot be used without QApplication

So, what is the reason signal&slot mechanism does not work in this case? How to make it working?

Upvotes: 4

Views: 4025

Answers (1)

The error message tells you exactly what you need to know: you can't use the event loop system without QCoreApplication existing. That's all. All of your exploration into the innards of Qt was educational, but a red herring. None if it matters at all.

only the receiver thread has to have the Qt event loop that will dispatch the event from the queue

That's correct.

Does it mean that if I create QCoreApplication inside QThread, this system should work?

You might create it on any thread (in contrast to QGuiApplication that can only live on the main thread). But make sure that you link statically with Qt. Otherwise, if you're linking with system Qt, you'll become binary incompatible with any process using the same Qt if you create a second instance of the application. Thus, if you use system Qt you can work around by inspecting whether an application instance exists, and only create one if it doesn't exist yet.

Furthermore, you shouldn't really need to create the application instance in a custom thread. Your library should accept an initialization call that should be performed in the main thread of the calling process. This initialization can create an application object if one doesn't exist.

// https://github.com/KubaO/stackoverflown/tree/master/questions/twothreads-41044526
#include <QtCore>

// see http://stackoverflow.com/questions/40382820
template <typename Fun> void safe(QObject * obj, Fun && fun) {
    Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
    if (Q_LIKELY(obj->thread() == QThread::currentThread()))
        return fun();
    struct Event : public QEvent {
      using F = typename std::decay<Fun>::type;
      F fun;
      Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
      Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
      ~Event() { fun(); }
    };
    QCoreApplication::postEvent(
          obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}

class Worker : public QObject {
   Q_OBJECT
   QBasicTimer m_timer;
   int n = 0;
   void timerEvent(QTimerEvent *event) override {
      if (event->timerId() == m_timer.timerId())
         emit hasData(n++);
   }
public:
   Q_SIGNAL void hasData(int);
   Q_SLOT void onData(int d) { qDebug() << QThread::currentThread() << "got data" << d; }
   void start() {
      safe(this, [this]{ m_timer.start(50,this); });
   }
   void quit() {
      safe(this, [this]{ m_timer.stop(); thread()->quit(); });
   }
};

class Library {
   QByteArray dummy{"dummy"};
   int argc = 1;
   char *argv[2] = {dummy.data(), nullptr};
   QScopedPointer<QCoreApplication> app;
   static Library *m_self;
   struct {
      Worker worker;
      QThread thread;
   } m_jobs[3];
public:
   Library() {
      Q_ASSERT(!instance());
      m_self = this;
      if (!qApp) app.reset(new QCoreApplication(argc, argv));

      for (auto & job : m_jobs) {
         job.worker.moveToThread(&job.thread);
         job.thread.start();
         job.worker.start();
         QObject::connect(&job.worker, &Worker::hasData, &m_jobs[0].worker, &Worker::onData);
      }
   }
   ~Library() {
      for (auto &job : m_jobs) {
         job.worker.quit();
         job.thread.wait();
      }
   }
   static Library *instance() { return m_self; }
};
Library *Library::m_self;

// API
void initLib() {
   new Library;
}

void finishLib() {
   delete Library::instance();
}

int main()
{
   initLib();
   QThread::sleep(3);
   finishLib();
}

#include "main.moc"

Upvotes: 2

Related Questions