为什么即使在使用 Qt::DirectConnection 之后,接收方的线程中仍会调用插槽?我如何确保它在另一个线程中被调用?
Why is a slot being called in receiver's thread even after using Qt::DirectConnection? How do I ensure that it is called in the other thread?
根据Qt5中的documentation for Qt::ConnectionType,使用Qt::DirectConnection
意味着给定信号的槽在与信号本身相同的线程中被调用,即使槽所属的对象位于在不同的线程中。
在我的应用程序中,我正在创建一个服务器,当收到一个新连接时,我创建一个新线程,其中有一个新的 QWebSocket
对象,并将某些 QWebSocket
信号连接到在服务器的 class 中定义的插槽(与接收连接和创建线程的 class 相同)。
但是虽然线程创建成功,但是slot在主线程中被调用
这是一个模拟我正在做的事情的更简单的例子,一个 MCVE:
Base.h 文件:
#ifndef BASE_H
#define BASE_H
#include<QThread>
#include<thread>
#include<QObject>
#include "emitcaller.h"
#include <QDebug>
class Base : public QObject
{
Q_OBJECT
public:
EmitCaller *emitCaller;
void create_thread();
void make_emit();
public slots:
void do_something();
};
#endif // BASE_H
这代表服务器 class。 create_thread()
就像要完成来自客户端的新连接时的功能。 do_something()
是QWebSocket
收到信号时需要执行的slot。
Base.cpp 文件:
#include "base.h"
#include "emitcaller.h"
#include<QEventLoop>
#include <mutex>
#include <condition_variable>
void Base::create_thread()
{
std::mutex mutex;
std::condition_variable cv;
std::thread t = std::thread([&](){
EmitCaller *ec = new EmitCaller;
this->emitCaller = ec;
qDebug() << "thread created, now in thread " << QThread::currentThread();
QObject::connect(ec,SIGNAL(my_signal()),this,SLOT(do_something()),Qt::DirectConnection);
cv.notify_all();
QEventLoop loop;
loop.exec();
});
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock); //wait till connect() completes, so that signal sent is received after that
t.detach();
}
void Base::do_something()
{
qDebug() << "doing something in thread " << QThread::currentThread();
}
void Base::make_emit()
{
qDebug() << "called make_emit in thread " << QThread::currentThread();
emitCaller->do_emit();
}
接下来,EmitCaller.h 文件:
#ifndef EMITCALLER_H
#define EMITCALLER_H
#include <QObject>
#include <QDebug>
class EmitCaller : public QObject
{
Q_OBJECT
public:
void do_emit();
signals:
void my_signal();
};
#endif // EMITCALLER_H
这是为了模拟QWebSocket
。 my_signal()
信号就是程序中的QWebSocket
收到的调用do_something()
槽的信号。 make_emit()
是一个额外的功能,只是要求发出信号,只是为了这个简化的例子而创建的。
EmitCaller.cpp 文件:
#include "emitcaller.h"
void EmitCaller::do_emit()
{
emit my_signal();
}
main.cpp 文件:
#include <QApplication>
#include "base.h"
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Base b;
qDebug() << "main in thread " << QThread::currentThread();
b.create_thread();
b.make_emit();
return a.exec();
}
输出结果如下:
main in thread QThread(0xc20180)
thread created, now in thread QThread(0x7fb9680009e0)
called make_emit in thread QThread(0xc20180)
doing something in thread QThread(0xc20180)
现在,根据我的理解,会发生以下情况:
create_thread()
被调用。 EmitCaller
对象(QWebSocket
对象)是在新线程中创建的。因此,对象的线程亲和力应该是新线程,从它发送的所有信号都应该来自新线程。
connect()
使用 Qt::DirectConnection
完成。因此,槽 do_something()
应该在这个新线程中调用,即使它的 class 对象 b
位于主线程中。
do_emit()
在其线程亲缘关系与新线程相关联的对象上调用,这应该会导致如上所述的预期行为。
我希望输出为:
main in thread QThread(0xc20180)
thread created, now in thread QThread(0x7fb9680009e0)
called make_emit in thread QThread(0xc20180)
doing something in thread QThread(0x7fb9680009e0)
补充几点:
- 在我的服务器程序中,我没有指向
QWebSocket
对象的指针。但是,当新客户端连接时会生成信号。为了自己发送信号,我创建了一个指针来访问该对象。
- 我需要使用
std::thread
,我不能为此使用 QThread
。
为什么在接收者的线程中调用槽,而不是在
发射器的线程,即使使用 Qt::DirectConnection
?
如果这不正确,我哪里错了? (我是 Qt 信号槽系统的新手)。
如果无法通过这种方式完成,我该如何实现我想要的行为?我想在一个单独的线程中 do_something()
到 运行。
谢谢。
您必须在问题陈述中考虑 3 个线程:
- 接收者所在的线程
- 发件人所在的线程
- 正在发出信号的线程
Queued/BlockingQueued 连接确保插槽将在接收器线程中执行。
DirectConnection 在 current 线程中执行槽。这并不总是发件人所在的线程!事实上,没有标准的方法来在 发送方的 线程中强制 运行 一个槽(因为通常,接收方的线程就是你想要的)。
注意:如果当前线程和 receiver 线程不同,AutoConnection 使用 QueuedConnection,否则使用 DirectConnection
为了解决你的问题,如果你在另一个线程上,你可以强制切换到发件人的线程,就像这样:
在 EmitCaller 中,添加
private: Q_INVOKABLE my_thread_do_emit() { do_emit(); }
然后在执行中:
void EmitCaller::do_emit()
{
if (this->thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "my_thread_do_emit", Qt::BlockingQueuedConnection);
} else {
emit my_signal();
}
}
但是,我建议您重新考虑您的设计。在某个外来线程中调用slot似乎不太正常。也许你的线程关联设置有问题...(例如,接收者应该在新创建的线程中)
根据Qt5中的documentation for Qt::ConnectionType,使用Qt::DirectConnection
意味着给定信号的槽在与信号本身相同的线程中被调用,即使槽所属的对象位于在不同的线程中。
在我的应用程序中,我正在创建一个服务器,当收到一个新连接时,我创建一个新线程,其中有一个新的 QWebSocket
对象,并将某些 QWebSocket
信号连接到在服务器的 class 中定义的插槽(与接收连接和创建线程的 class 相同)。
但是虽然线程创建成功,但是slot在主线程中被调用
这是一个模拟我正在做的事情的更简单的例子,一个 MCVE:
Base.h 文件:
#ifndef BASE_H
#define BASE_H
#include<QThread>
#include<thread>
#include<QObject>
#include "emitcaller.h"
#include <QDebug>
class Base : public QObject
{
Q_OBJECT
public:
EmitCaller *emitCaller;
void create_thread();
void make_emit();
public slots:
void do_something();
};
#endif // BASE_H
这代表服务器 class。 create_thread()
就像要完成来自客户端的新连接时的功能。 do_something()
是QWebSocket
收到信号时需要执行的slot。
Base.cpp 文件:
#include "base.h"
#include "emitcaller.h"
#include<QEventLoop>
#include <mutex>
#include <condition_variable>
void Base::create_thread()
{
std::mutex mutex;
std::condition_variable cv;
std::thread t = std::thread([&](){
EmitCaller *ec = new EmitCaller;
this->emitCaller = ec;
qDebug() << "thread created, now in thread " << QThread::currentThread();
QObject::connect(ec,SIGNAL(my_signal()),this,SLOT(do_something()),Qt::DirectConnection);
cv.notify_all();
QEventLoop loop;
loop.exec();
});
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock); //wait till connect() completes, so that signal sent is received after that
t.detach();
}
void Base::do_something()
{
qDebug() << "doing something in thread " << QThread::currentThread();
}
void Base::make_emit()
{
qDebug() << "called make_emit in thread " << QThread::currentThread();
emitCaller->do_emit();
}
接下来,EmitCaller.h 文件:
#ifndef EMITCALLER_H
#define EMITCALLER_H
#include <QObject>
#include <QDebug>
class EmitCaller : public QObject
{
Q_OBJECT
public:
void do_emit();
signals:
void my_signal();
};
#endif // EMITCALLER_H
这是为了模拟QWebSocket
。 my_signal()
信号就是程序中的QWebSocket
收到的调用do_something()
槽的信号。 make_emit()
是一个额外的功能,只是要求发出信号,只是为了这个简化的例子而创建的。
EmitCaller.cpp 文件:
#include "emitcaller.h"
void EmitCaller::do_emit()
{
emit my_signal();
}
main.cpp 文件:
#include <QApplication>
#include "base.h"
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Base b;
qDebug() << "main in thread " << QThread::currentThread();
b.create_thread();
b.make_emit();
return a.exec();
}
输出结果如下:
main in thread QThread(0xc20180)
thread created, now in thread QThread(0x7fb9680009e0)
called make_emit in thread QThread(0xc20180)
doing something in thread QThread(0xc20180)
现在,根据我的理解,会发生以下情况:
create_thread()
被调用。EmitCaller
对象(QWebSocket
对象)是在新线程中创建的。因此,对象的线程亲和力应该是新线程,从它发送的所有信号都应该来自新线程。connect()
使用Qt::DirectConnection
完成。因此,槽do_something()
应该在这个新线程中调用,即使它的 class 对象b
位于主线程中。do_emit()
在其线程亲缘关系与新线程相关联的对象上调用,这应该会导致如上所述的预期行为。
我希望输出为:
main in thread QThread(0xc20180)
thread created, now in thread QThread(0x7fb9680009e0)
called make_emit in thread QThread(0xc20180)
doing something in thread QThread(0x7fb9680009e0)
补充几点:
- 在我的服务器程序中,我没有指向
QWebSocket
对象的指针。但是,当新客户端连接时会生成信号。为了自己发送信号,我创建了一个指针来访问该对象。 - 我需要使用
std::thread
,我不能为此使用QThread
。
为什么在接收者的线程中调用槽,而不是在 发射器的线程,即使使用
Qt::DirectConnection
?如果这不正确,我哪里错了? (我是 Qt 信号槽系统的新手)。
如果无法通过这种方式完成,我该如何实现我想要的行为?我想在一个单独的线程中
do_something()
到 运行。
谢谢。
您必须在问题陈述中考虑 3 个线程:
- 接收者所在的线程
- 发件人所在的线程
- 正在发出信号的线程
Queued/BlockingQueued 连接确保插槽将在接收器线程中执行。
DirectConnection 在 current 线程中执行槽。这并不总是发件人所在的线程!事实上,没有标准的方法来在 发送方的 线程中强制 运行 一个槽(因为通常,接收方的线程就是你想要的)。
注意:如果当前线程和 receiver 线程不同,AutoConnection 使用 QueuedConnection,否则使用 DirectConnection
为了解决你的问题,如果你在另一个线程上,你可以强制切换到发件人的线程,就像这样:
在 EmitCaller 中,添加
private: Q_INVOKABLE my_thread_do_emit() { do_emit(); }
然后在执行中:
void EmitCaller::do_emit()
{
if (this->thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "my_thread_do_emit", Qt::BlockingQueuedConnection);
} else {
emit my_signal();
}
}
但是,我建议您重新考虑您的设计。在某个外来线程中调用slot似乎不太正常。也许你的线程关联设置有问题...(例如,接收者应该在新创建的线程中)