为什么即使在使用 Qt::D irectConnection 之后,接收器的线程中仍会调用插槽?如何确保在另一个线程中调用它?

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?

本文关键字:调用 线程 插槽 何确保 另一个 确保 之后 irectConnection 接收器 为什么 Qt      更新时间:2023-10-16

根据Qt5中Qt::ConnectionType的文档,使用Qt::DirectConnection意味着给定信号的插槽与信号本身在同一线程中调用,即使插槽所属的对象位于不同的线程中。

在我的应用程序中,我正在创建一个服务器,当收到新连接时,我创建一个新线程,其中的新QWebSocket对象,并将某些QWebSocket信号连接到服务器类中定义的插槽(接收连接和创建线程的同一类)。

但是,尽管线程已成功创建,但正在主线程中调用该槽。


这是一个更简单的例子,模拟我正在做的事情,一个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

这表示服务器类。create_thread()就像要完成来自客户端的新连接时的函数。do_something()QWebSocket接收到信号时需要执行的插槽。

基本.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

这是为了模拟QWebSocketmy_signal()信号是程序中QWebSocket接收的用于调用do_something()槽的信号。make_emit()是一个额外的函数,只是为了请求要发出的信号,只是为了这个简化的示例而创建的。

发射器呼叫者.cpp文件:

#include "emitcaller.h"
void EmitCaller::do_emit()
{
emit my_signal();
}

主.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)

现在,根据我的理解,会发生以下情况:

  1. create_thread()被称为。EmitCaller对象(QWebSocket对象)在新线程内创建。因此,对象的线程亲和力应该是新线程,从它发送的所有信号都应该来自新线程。
  2. connect()是使用Qt::DirectConnection完成的。因此,应该在这个新线程中调用插槽do_something(),即使它的类对象b位于主线程中。
  3. 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)

补充几点:

  1. 在我的服务器程序中,我没有指向QWebSocket对象的指针。但是,当新客户端连接时,会生成信号。为了自己发送信号,我创建了一个指针来访问该对象。
  2. 我需要使用std::thread,我不能为此使用QThread

  • 为什么在接收器的线程中调用插槽,而不是 发射器的线程,即使使用了Qt::DirectConnection

  • 如果这是不正确的,我哪里出错了?(我是Qt信号槽系统的新手)。

  • 如果不能这样做,我怎样才能实现我想要的行为?我想do_something()在单独的线程中运行。

谢谢。

您必须在问题陈述中考虑 3 个线程:

  1. 接收方所在的线程
  2. 发送方所在的线程
  3. 发出信号的线程

排队/阻塞排队连接确保插槽将在接收器线程中执行。

直接连接执行当前线程中的插槽。这并不总是发件人所在的线程!事实上,没有标准方法可以强制在发送方线程中运行插槽(因为通常,接收方的线程是您想要的)。

注意:如果当前线程和接收线程不同,则自动连接使用队列连接,否则直接连接

为了解决您的问题,如果您在另一个线程上,您可以强制切换到发件人的线程,如下所示:

在"发射器呼叫者"中,添加

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

但是,我建议您重新考虑您的设计。在某个外来线程中调用插槽似乎不寻常。也许您的线程相关性设置有问题...(例如,接收器应该存在于新创建的线程中)