Qt 5:从非Qt线程发出信号

时间:2016-12-08 16:42:28

标签: c++ qt qt5

我想要实现的是在Qt QTcpServer / Socket之上构建的跨平台TCP套接字库。我遇到了一个问题,即QThread中没有Qt事件循环的非Qt线程发出的信号没有被事件循环接收到。

根据thisthis问题,我发现从非Qt线程发出的Qt :: QueuedConnection连接类型明确设置。这些问题相当陈旧,与Qt 4有关。所以我想知道Qt 5是否仍支持此功能。

我已经探索了Qt 5源代码并发现:

  1. 发出信号只是拨打QMetaObject::activate
  2. 反过来,
  3. QMetaObject::activate调用queued_activate,如果连接类型设置为Qt :: QueuedConnection,或者当前线程(发射器线程)与线程接收器不同(在我的情况下, Qt :: QueuedConnection是明确设置的。)
  4. queued_activate创建一个事件对象并调用QCoreApplication::postEvent
  5. QCoreApplication::postEvent执行正确锁定并将事件放入接收器事件队列中。尽管postEvent是一个静态QCoreApplication函数,它使用self - 一个指向当前静态QCoreApplication单例的指针,但即使没有全局QCoreApplication对象(即self == 0),它也应该正常工作。
  6. 鉴于此,我认为为了使信号和插槽机制正常工作,只有接收器线程必须有Qt事件循环才能从队列中调度事件,如果我错了就纠正我。

    尽管如此,从非Qt线程发出信号对我来说不起作用。我创建了尽可能简单的演示应用程序来演示信号和插槽的故障。

    MyThread组件只是继承QThread并在其自身内部移动(moveToThread)QObject派生的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);
    }
    

    线程工作者需要在MyThread线程中存活并连接到MyThread的mySignal()信号。

    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!";
    }
    

    最后,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();
    }
    

    注意,如果我取消注释QCoreApplication的创建,我会得到正确的输出:

    mySignal emitted
    mySlot called! It works!
    

    但如果我按原样离开,我只能

    mySignal emitted
    QEventLoop: Cannot be used without QApplication
    

    那么,在这种情况下,信号和插槽机制不起作用的原因是什么?如何使它工作?

1 个答案:

答案 0 :(得分:2)

错误消息告诉您完全您需要知道的内容:您不能使用不存在QCoreApplication的事件循环系统。就这样。你对Qt内部的所有探索都是有教育意义的,但却是一种红色的鲱鱼。没有,如果它很重要。

  

只有接收者线程必须具有Qt事件循环,该循环将从队列中分派事件

这是正确的。

  

这是否意味着如果我在QThread中创建QCoreApplication,这个系统应该有用吗?

您可以在任何线程上创建它(与只能在主线程上生存的QGuiApplication相反)。但请确保您与Qt静态链接。否则,如果您正在使用系统Qt进行链接,那么如果您创建应用程序的第二个实例,则您将变为与使用相同Qt的任何进程不兼容的二进制文件。因此,如果您使用系统Qt,您可以通过检查应用程序实例是否存在来解决,如果它还不存在则只创建一个。

此外,您不应该真正需要在自定义线程中创建应用程序实例。您的库应该接受应该在调用进程的主线程中执行的初始化调用。如果不存在,则此初始化可以创建应用程序对象。

// 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"