关于主线程调用子线程函数发送信号,子线程槽函数进不去问题,绕的冤枉路记录

QT串口通信线程陷阱

背景:

1 移植LINUX下C语言的程序到WIN下QT环境,然后再移植到LINUX的QT环境生成执行程序。

2 主线程使用moveToThread()创建子线程。

3 子线程初始化时,创建串口,打开串口,while(true)接收串口数据。

想实现主界面点击按钮,让串口线程发送一包报文

按以往的经验就是,主线程调用子线程函数,子线程函数内发送信号,自己接收信号进入槽函数进行发送报文,这样可以做到发送在子线程进行不影响主线程。

问题来了:主线程的调用进入到子线程后,没有进入槽函数

最后发现原因:子线程在初始化时打开了while(true),整个线程处在卡在循环状态,后续再进入的操作都不会触发。

走的冤枉路:1、 首先想到的是连接问题,先去排查了信号槽的建立是否有问题,排查后确定没问题。只要connect参数没错,信号槽参数没错,几乎不会有问题。

2、有可能跨线程导致槽函数没进入。这个也是以前遇到的坑,但是已经特意使用调用子线程函数,然后发信号的方式规避了。

3、最大的坑来了:猜测是否是类的创建有问题,类继承QObject,就不好使。换成有界面类型继承QWidget试了下,发现软件启动卡死,暂时关掉接收循环,发现发送成功了,当时完全搞迷糊了,后来明白了才发现有多低级。

当时在测试换成继承QWidget类时,就预感自己走弯路了,但是实在没想出来原因,只能硬着头皮继续查。最后发现主线程的ID和子线程的ID是一样的,说明根本就没开辟子线程,所以启动while循环时卡死了。说明继承QWidget带UI界面的类,使用moveToThread不好使。

然后发送好使是因为while关掉了,自然好使。

总结:以前使用QT时没发现这个问题,是因为以前使用QT的串口接收,调用的是信号槽,当收到信号readyRead(),就进入槽函数解析。这不同于while循环,不会造成卡死。所以以前的串口接收发送都写到一个cpp里。

现在要移植到linux下的QT,无法使用QT自带的串口类,所以忽略了这个问题。

其次是LINUX下C语言写的串口通讯和C++还是有区别,C语言没有类,可以直接创建一个线程专门供while循环接收数据,而其他发送在线程外。C++是一个线程专门给一个类使用,循环就造成整个类的函数都不能再运行。

<think>我们讨论的是子线程A发送信号触发子线程B的槽函数。关键点在于确保信号与槽的连接是跨线程的,并且使用队列连接方式(Qt::QueuedConnection),这样信号才会被正确路由到子线程B的事件循环中。 步骤: 1. 创建两个子线程A和B,以及两个工作对象WorkerA和WorkerB。 2. 将WorkerA移动到线程A,将WorkerB移动到线程B。 3. 在子线程A中创建信号,在子线程B中创建槽函数。 4. 连接信号和槽,并指定连接型为Qt::QueuedConnection(跨线程连接默认就是队列连接,但显式指定更安全)。 5. 启动两个线程的事件循环。 6. 在子线程A中发射信号子线程B的槽函数就会被触发。 注意: - 必须确保两个线程都启动了事件循环(即调用了exec()或通过QThread::start()启动,因为QThread的run()默认会调用exec())。 - 对象必须被正确地移动到目标线程。 下面是一个代码示例: </think>### 子线程A发送信号触发子线程B槽函数的实现方法 #### **一、核心原理** 子线程间通信的关键在于**正确的线程依附性设置**和**队列连接方式**,确保信号被路由到目标线程的事件队列: 1. 每个线程对象必须有自己的事件循环(通过`QThread::exec()`启动) 2. 槽函数对象必须通过`moveToThread()`绑定到目标线程 3. 连接型必须为`Qt::QueuedConnection`(跨线程连接默认使用) #### **二、完整实现步骤** ##### 1. 创建工作对象和线程 ```cpp // 线程B的工作对象 class WorkerB : public QObject { Q_OBJECT public slots: void handleSignal(int data) { qDebug() << "[线程B] 接收数据:" << data << "| 线程ID:" << QThread::currentThreadId(); } }; // 线程A的工作对象 class WorkerA : public QObject { Q_OBJECT public: void triggerSignal() { qDebug() << "[线程A] 发射信号"; emit sendData(42); // 触发信号 } signals: void sendData(int data); // 线程A的信号 }; // 主线程中创建对象 QThread* threadA = new QThread(); QThread* threadB = new QThread(); WorkerA* workerA = new WorkerA(); // 创建后默认依附主线程 WorkerB* workerB = new WorkerB(); ``` ##### 2. 设置线程依附性(关键步骤) ```cpp workerA->moveToThread(threadA); // 将workerA转移到线程A workerB->moveToThread(threadB); // 将workerB转移到线程B // 验证转移结果 Q_ASSERT(workerA->thread() == threadA); Q_ASSERT(workerB->thread() == threadB); ``` ##### 3. 建立跨线程连接 ```cpp // 连接线程A的信号 -> 线程B的槽 connect(workerA, &WorkerA::sendData, workerB, &WorkerB::handleSignal, Qt::QueuedConnection); // 显式指定队列连接 ``` ##### 4. 启动线程并触发信号 ```cpp threadA->start(); // 启动线程A的事件循环 threadB->start(); // 启动线程B的事件循环 // 通过线程A的槽函数触发信号(线程安全) QMetaObject::invokeMethod(workerA, "triggerSignal", Qt::QueuedConnection); ``` #### **三、关键注意事项** 1. **事件循环必须存在** 子线程需运行`exec()`才能处理队列信号: ```cpp void QThread::run() override { exec(); // 必须调用!否则无法响应信号[^3] } ``` 2. **避免直接调用槽函数** 错误方式: ```cpp workerA->triggerSignal(); // ❌ 直接调用会导致在调用者线程执行 ``` 正确方式: ```cpp // 使用元对象系统跨线程调用 QMetaObject::invokeMethod(workerA, "triggerSignal", Qt::QueuedConnection); ``` 3. **线程安全退出** ```cpp // 安全释放资源 connect(threadA, &QThread::finished, workerA, &QObject::deleteLater); connect(threadB, &QThread::finished, workerB, &QObject::deleteLater); threadA->quit(); // 请求退出 threadA->wait(); // 等待线程结束 ``` #### **四、试技巧** 当槽函数未触发时排查: 1. 检查线程状态: ```cpp qDebug() << "ThreadA running:" << threadA->isRunning(); qDebug() << "ThreadB running:" << threadB->isRunning(); ``` 2. 验证连接状态: ```cpp bool connected = connect(workerA, SIGNAL(sendData(int)), workerB, SLOT(handleSignal(int))); qDebug() << "连接状态:" << connected; ``` 3. 打印线程ID: ```cpp // 在槽函数中添加 qDebug() << "当前线程ID:" << QThread::currentThreadId(); ``` > 此方案通过`Qt::QueuedConnection`自动将信号加入线程B的事件队列,确保跨线程调用安全[^1]。实际输出示例: > ``` > [线程A] 发射信号 | 线程ID: 0x7f9ac400 > [线程B] 接收数据:42 | 线程ID: 0x7f9ac800 > ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值