我想要实现的是在Qt QTcpServer / Socket之上构建的跨平台TCP套接字库。我遇到了一个问题,即QThread中没有Qt事件循环的非Qt线程发出的信号没有被事件循环接收到。
根据this和this问题,我发现从非Qt线程发出的Qt :: QueuedConnection连接类型明确设置。这些问题相当陈旧,与Qt 4有关。所以我想知道Qt 5是否仍支持此功能。
我已经探索了Qt 5源代码并发现:
鉴于此,我认为为了使信号和插槽机制正常工作,只有接收器线程必须有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
那么,在这种情况下,信号和插槽机制不起作用的原因是什么?如何使它工作?
答案 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"