Qt跨线程信号槽槽函数无响应(未调用)问题

最近在开发中遇到一个很奇怪的问题 ,槽函数与信号连接后,在代码执行中发出信号,槽函数始终进不去。一开始认为是connect调用传参不对,打了下返回值是true,而且在输出中也没有相关warnning输出。信号连接和调用处打断点所处线程为子线程,跨线程信号槽调用,所以猜测跟多线程有关。排查过程中各种换信号、改参数都没有效果,偶然间把接收对象的创建移至主线程,槽函数居然执行成功了。本着有问题看手册的原则,在手册中找到了如下的说明:
在这里插入图片描述
大体意识是说:Qobject对象是有线程归属的,或者说其存活在特定线程中。当接收到队列连接的信或 投递的事件,槽函数或事件处理在其归属线程中执行。下面的图比较形象
在这里插入图片描述
重点是下面的Note: 如果一个对象没有归属线程(也就是thread()接口调用返回值为空),或者其所属线程没有运行中的消息循环,队列连接的信号或投递的事件是无法正常接收的。
下面的话也很重要:QObject的默认归属线程是创建这个对象的线程,具体属于哪个线程对象可通过thread()接口查询,如果要改变其归属线程可调用moveToThread()接口。
好,先记住这段话,对后面问题分析很有用。接下来通过代码描述问题出现的场景:


GUITest::GUITest(QWidget *parent)
	: QMainWindow(parent)
{
	.....
	// 启线程,延迟5s发信号
	CTestThread* ptr_thread = new CTestThread(this);
	QTimer::singleShot(5000, this, [this]() { 
		qDebug() << "==== SignalTestSlot";
		emit SignalTestSlot(); 
	});

	ptr_thread->start();
}
///

class CSignalSlot : public QObject 
{
	Q_OBJECT

public: 
	CSignalSlot(QObject* ptr_parent = nullptr);
	
public slots:
	void OnSlot();
};

class GUITest;
class CTestThread : public QThread
{
	Q_OBJECT

public: 
	CTestThread(GUITest* ptr_test, QObject* ptr_parent = nullptr);
	virtual void run() override;

private: 
	GUITest* ptr_test_{ nullptr };
	CSignalSlot* ptr_owned_{ nullptr };
};



CTestThread::CTestThread(GUITest* ptr_test, QObject* ptr_parent /*= nullptr*/)
	: QThread(ptr_parent), 
	ptr_test_(ptr_test)
{
	if(ptr_owned_  != nullptr)
		delete ptr_owned_;
}

// 线程执行的Run函数
void CTestThread::run()
{
	while (true)
	{
		QThread::sleep(2);
		if (ptr_owned_ == nullptr)
		{
			// 子线程中创建对象,所以ptr_owned_ 所属对象为子线程
			ptr_owned_ = new CSignalSlot;
			connect(ptr_test_, &GUITest::SignalTestSlot, ptr_owned_, &CSignalSlot::OnSlot, Qt::QueuedConnection);
		}
	}
}

CSignalSlot::CSignalSlot(QObject* ptr_parent /*= nullptr*/)
	: QObject(ptr_parent)
{
}

// 槽函数执行
void CSignalSlot::OnSlot()
{
	qDebug() << "==== onslot";
}

为了模拟跨线程的场景,代码中在子线程的run函数中创建CSignalSlot 对象,然后在主线程中GUITest构造函数中延迟发送信号,保证信号发送时receiver已创建,并关联了信号槽,这是怎么看都不觉得有问题的代码,测试中却发现槽函数不会执行。按照之前分析,可能原因是CSignalSlot 所属线程的消息循环未启动或已退出,即未处于运行状态。手册中有说明:调用QThread的exec会开消息循环,调用start接口默认会执行exec,因此也会开消息循环。问题就出在下面这段话:

The starting point for the thread. After calling start(), the newly created thread calls this function. The default implementation simply calls exec().
start启线程后是否执行exec取决于run接口的实现,默认是调exec;如果重写了run接口,修改了默认行为,不再执行exec,也就不会运行消息循环。因此TestThread创建的CSignalSlot对象(跨线程队列连接的)槽函数是不会执行的。由此也可以推断,标准线程std::thread如果结合QObject使用信号槽,槽函数也是不能执行的。
分析的结论有点像是跨线程信号槽的错误打开方式,那就重来吧。针对跨线程的信号槽使用,只能用QThread->start(),然后moveToThread将对象移至目标线程,这样可以保留消息循环,让槽函数正确执行,这大概也是moveToThread的意义所在。
对QObject在多线程场景下的使用,以及接口调用的可重入性,在手册中也有说明(如下截图),这些规则是比较容易被忽略的。
在这里插入图片描述
QObject设计是只在单线程中使用的,对父子对象也有同线程的约束。其实也不难理解,Qt对象管理的最大特色在于父子关系的管理,而这也是其弱点所在,因为对对象中保存了父对象的指针,很多接口都是非线程安全的,调用逻辑跟父、子对象有复杂的关联关系,之前我们讲到对象是有归属线程的,如果这时候父子线程不在同一线程,临界数据的访问就会引入很多问题,所以对象只在单个线程中用这是基本约束。文档中还强调QThread的run接口中创建的对象不要将QThread对象指定为父对象,二者归属不同的线程,这是很显然的。

另有交代(NOTE)
跨线程的信号槽使用,上面只分析了线程未启EventLoop的场景,如果上述方法对你现在的问题不适用。还有一个常见的原因:信号中带自定义的数据类型, 原则上讲,所有队列连接的信号槽,自定义类型的参数,都要注册元类型。一个元类型注册就可能引入很多问题,不妨检查:

  1. 排查是否有遗漏了注册元类型;
  2. 注册时的类型名称与信号中的类型名称是否匹配,经常会出现注册时带了命名空间,信号使用时又没带命名空间的情况;
  3. 槽函数的参数类型与注册类型是否一致,比如都带或都没带命名空间。

此外,一个自定义类型不能够多次注册,关注下VS的输出,能够看到对应的warnning。多次注册时qt会视为运行时异常,直接abort。
还有一点小技巧,如果某个类型是三方库定义的,你想作为你的信号参数用,又担心多次注册问题(还是有可能的,首先三方库设计上肯定不可能注册元类型,一是没考虑这种场景,二压根不用qt库;那么上层应用随时可能拿来注册下)。此时,可以通过Typedef,定义一个唯一的别名,然后对这个别名注册元类型,亲测可用。
如果还是不行,那就看看连接之后是否有disconnect的地方,或者connect时候发送信号的对象是否有创建,经常对象还没创建就connect,槽函数自然是进不去的。这种情况qt会有warning输出,万能的VS输出用起来。
好了,都交代完了,如果解决了你的问题记得点赞收藏。

### 回答1: Qt是一款流行的平台C++框架,有着强大的功能和丰富的类库。Qt的核心机制包括Qt的元对象系统和信号机制。 Qt的元对象系统是Qt的一个重要特性,它是Qt实现反射的基础。在C++中,反射能够在运行时获取类的信息,如类名、属性、方法等,并在运行时动态地创建、调用对象。Qt的元对象系统通过为每个QObject派生的子类生成一个元对象,实现了C++的反射机制。元对象系统使得Qt能够在运行时获取QObject派生类的信息,并提供了一系列函数来操作这些对象。 Qt信号机制是Qt的核心机制之一,它用于实现对象之间的通信。信号机制基于发布-订阅模式,其中一个对象发送信号,而另一个对象通过连接到这个信号函数来接收信号并进行相应的处理。信号机制具有松耦合的特性,可以实现对象之间的解耦。 在信号机制中,信号是由QObject派生类定义的特殊函数,用于声明某个特定事件发生时要发送的信号函数是QObject派生类中的普通函数,用于接收这个信号,并且执行相应的处理逻辑。信号通过信号连接函数进行连接,这样当信号触发时,与之连接的函数就会被自动调用Qt的元对象系统和信号机制是Qt强大功能的基石。元对象系统实现了C++的反射机制,允许在运行时获取和操作对象的信息。信号机制使对象之间的通信变得简单和易用,提供了一种灵活而高效的方式来实现对象间的解耦。通过这些核心机制,Qt能够帮助开发人员更快速、更简便地开发高质量的平台应用程序。 ### 回答2: qt核心机制是指Qt框架的底层机制,主要包括Qt元对象系统和Qt信号原理。 Qt元对象系统是Qt框架中的一个重要概念,它在C++语言的基础上添加了一套元对象(Meta Object)系统。元对象系统在编译过程中生成了额外的代码,使得我们可以在运行时获得更多的对象信息。通过元对象系统,Qt实现了信号机制、宏(MOC)编译和反射等功能。元对象系统实际上是一种面向对象的编程方式,通过它可以实现Qt特有的功能,如动态属性、动态信号等。 Qt信号原理是Qt框架中的一个重要特性,用于对象间的通信。信号是一种异步通信方式,通过信号发送者(Sender)发送信号,接收者(Receiver)通过函数(Slot)响应信号信号是通过元对象系统实现的,编译器会在MOC编译阶段解析信号的声明,并在运行时建立连接关系。这种机制使得Qt程序的耦合性更低,灵活性更高,同时也为多线程编程提供了一种方便的方式。 总的来说,Qt核心机制包括了Qt的元对象系统和信号原理。元对象系统为Qt框架提供了反射、动态属性和动态信号等功能,信号机制实现了对象间的异步通信。这些机制使得Qt框架具有高度的可扩展性、灵活性和平台性,为开发者提供了一种便捷、高效的方式来构建应用程序。 ### 回答3: Qt是一种平台的应用程序框架,具有丰富的功能和强大的性能。Qt核心机制是指Qt框架的基础机制,包括Qt元对象系统和Qt信号原理。 Qt元对象系统是Qt框架的核心组成之一,用于实现Qt的一些特殊功能,如信号机制和动态属性。Qt元对象系统通过将所有的类对象都派生自QObject基类,实现了一种反射机制,使得对象之间可以动态地连接和交互。通过使用元对象系统,Qt可以实现面向对象编程的高级特性,如对象间的信号的连接,对象的属性系统以及对象的内省(即动态获取对象的属性和方法信息)等。 Qt信号原理是Qt框架实现事件驱动的关键机制。信号机制允许不同对象之间进行松散的耦合,通过信号的方式进行通信。信号是一种特殊的成员函数,用于表示某个事件的发生,是一种普通的成员函数,用于响应信号的发出。当一个信号被发出时,Qt框架会自动将信号进行匹配,并调用对应的函数。这种机制使得对象之间的通信更加灵活和高效,可以实现事件的传递和处理,避免了显式的函数调用和回调函数的使用。 综上所述,Qt的核心机制包括Qt元对象系统和Qt信号原理。通过元对象系统,Qt实现了一种反射机制,使得对象之间可以动态地连接和交互;通过信号机制,Qt实现了一种松散耦合的事件处理方式,提高了对象之间的通信效率和灵活性。这些机制是Qt框架的重要组成部分,为开发者提供了更加强大和易用的工具和功能。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值