【问题标题】:Qt 5: emit signal from non-Qt threadQt 5:从非 Qt 线程发出信号
【发布时间】:2016-12-08 16:42:27
【问题描述】:

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

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

我探索了Qt 5源代码,发现:

  1. 发出信号只是调用QMetaObject::activate
  2. QMetaObject::activate 反过来调用 queued_activate,如果连接类型设置为 Qt::QueuedConnection 或当前线程(发射线程)与线程接收者不同(在我的情况下,Qt::QueuedConnection 是显式设置的)。
  3. queued_activate 创建一个事件对象并调用 QCoreApplication::postEvent
  4. QCoreApplication::postEvent 进行适当的锁定并将事件放入 receiver 事件队列。尽管 postEvent 是一个使用 self 的静态 QCoreApplication 函数 - 一个指向当前静态 QCoreApplication 单例的指针,但即使没有全局 QCoreApplication 对象(即 self == 0),它也应该可以正常工作。

鉴于此,我认为要让信号&槽机制正常工作,只有接收器线程必须具有将从队列中分派事件的 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

那么,在这种情况下,signal&slot 机制不起作用的原因是什么?如何让它发挥作用?

【问题讨论】:

    标签: c++ qt qt5


    【解决方案1】:

    错误消息准确地告诉了您您需要知道的内容:如果没有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"
    

    【讨论】:

    • QCoreApplication 对象的生存位置是否重要(就线程关联而言)?这是否意味着如果我在 QThread 中创建 QCoreApplication,这个系统应该可以工作?
    • 是的,它应该可以工作,即使您永远不需要在线程中创建 QCoreApplication,因为 Qt 的事件循环利用本机事件循环,所以如果您将 DLL 注入到应用程序,您只需要应用程序的一个实例,但您不需要调用exec,只要底层应用程序泵送本机事件循环即可。
    猜你喜欢
    • 2014-09-18
    • 1970-01-01
    • 2015-09-27
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-16
    • 1970-01-01
    相关资源
    最近更新 更多