QObject: Cannot create children for a parent that is in a different thread

2023-11-10

一篇又臭又长的流水账,要看结论可以直接拉到最后。

 

在一个项目中,需要使用串口接收外部的对射管状态,然后调用传感器。由于在之前的项目中,自制了一个带有UI的串口管理类(继承QDialog)最早在主线程中生成这个串口管理类。但是发现程序变得越来越复杂以后,主线程会出现几十甚至几百毫秒的的连续占用,可能会导致串口响应不及时,状态刷新迟到,传感器采集不到完整的图像。

准备将带有UI的QSerialport通过Movetothread直接丢到子线程里运行,发现带有UI的类是不能这么干的...于是只能苦哈哈的重写这个串口管理类。

最早的想法是,将QSerialport直接Movetothread,发现可以编译通过,但是并没有卵用。仔细研究后发现,这个QSerialport是在串口管理类中直接声明的。由于串口管理类是在Mainwindow的初始化中new出来的,这个QSerialport自然也是在主线程中生成的。

class Serial_with_Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Serial_with_Dialog(QWidget *parent = nullptr, QString port_name  = "", QPushButton* button = nullptr);
    ~Serial_with_Dialog();
    
    QSerialPort serial_port;

....

于是改为只声明指针,在CPP中new一个QSerialPort。发现会提示QObject: Cannot create children for a parent that is in a different thread。此时程序虽然可以正常运行,但是对QSerialPort进行操作依旧是在主线程中进行。

头文件中:
class Serial_with_Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Serial_with_Dialog(QWidget *parent = nullptr, QString port_name  = "", QPushButton* button = nullptr);
    ~Serial_with_Dialog();
    
    QSerialPort* serial_port;

....



cpp文件中:
...

serial_port = new QSerialPort;

...

根据之前的研究经验,即使Movetothread后,如果直接调用函数,函数还是会在调用者的线程中运行。必须通过信号和槽进行QueuedConnection连接,才能通过槽让函数运行在子线程上。于是为QSerialport重新写了一个包含类Serial_Thread,继承了QObject,并将这个包含类movetothread。

头文件中:

class Serial_Thread : public QObject
{
    Q_OBJECT

public:
    explicit Serial_Thread(QWidget *parent = nullptr);
    ~Serial_Thread();

    QSerialPort* serial_port;
....



class Serial_with_Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Serial_with_Dialog(QWidget *parent = nullptr, QString port_name  = "", QPushButton* button = nullptr);
    ~Serial_with_Dialog();


    Serial_Thread* serial_thread;



CPP文件中:

Serial_with_Dialog::Serial_with_Dialog(QWidget *parent, QString port_name, QPushButton *button) :
    QDialog(parent) ,
    ui(new Ui::Serial_with_Dialog)
{
    ui->setupUi(this);


    //Language_Change(); //切换语言
    serial_thread = new Serial_Thread(nullptr);
serial_thread = new Serial_Thread(nullptr);

.....

这里在Serial_Thread中将QSerialPort* serial_port;暴露出来,主要是因为在Serial_with_Dialog中有许多直接调用QSerialPort进行串口开关、收发等操作的函数。为了减少对这些函数的修改,通过指针直接调用这个QSerialPort。经过一番修改,程序可以正常运行,但是在qdebug窗口中会出现错误提示:

QObject: Cannot create children for a parent that is in a different thread

字面意义是说,不能在一个线程中为不在同一个线程中的父类生成新的成员。由于程序可以正常运行,就没有在意。但是经过仔细的测试,发现虽然对这个QSerialPort进行读、写操作的函数,都在子线程中运行,但是实际上这个QSerialPort本身依旧是在主线程中进行处理的。通过while(1)卡死主线程后,这个QSerialPort就失去了响应。

经过网上一番搜索,也没有对应的方案。于是自己一点点思考,感觉可能是因为这个QSerialPort是在Serial_Thread的构造函数中生成的。因为构造函数是在主线程中运行的,这个QSerialPort必然也是在主线程中生成的,所以必然在主线程中处理。于是给Serial_Thread写了个Init函数,通过信号连接,等Serial_Thread所处的QThread运行start()以后再通过信号调用初始化。

void Serial_Thread::Init()
{

   qDebug()<<tr("[Serial_Thread]Init, time= %1, @ %2").arg(clock()).arg(QString::number(quintptr(QThread::currentThreadId())));
   serial_port = new QSerialPort(nullptr);   //新建
   QObject::connect(serial_port, &QSerialPort::readyRead, this, &Serial_Thread::Serial_Read,Qt::QueuedConnection); //连接读取函数
}

经过debug测试,这次Init确实是在子线程中运行了,但是QObject: Cannot create children for a parent that is in a different thread的提示依旧在,QSerialPort也依旧在主线程中运行。转了半天原来还在原地。

没有放弃,继续测试,在Serial_Thread中,其它的槽函数中另外new多个QSerialPort出来,发现只要不把这个QSerialPort的指针赋给serial_port,就不会出现前面的错误提示。甚至在Serial_Thread声明了一个无用的serial_port2,将new出来的QSerialPort赋值给它,也不会出现错误提示。

结合之前的实验仔细思考了一下,Serial_Thread实在主线程中生成的,属于Mainwindow。通过Movetothread到新线程中的QObject,自身应该是分裂(或者说复制)成了两分。原来的那一份还是在主线程,或者叫做父线程中运行,通过指针可以直接调用。在新线程中,生成了新的一份实体,其中跟父线程完全无关的部分成员,会在生成并且运行在子线程中,跟父线程有直接关联的部分,实际调用的还是在主线程中运行,一些成员变量则会保持同步。(纯猜想)

通过实验验证,在子线程中new一个QSerialPort,只要不将其指针赋值给serial_port,而是赋值给无用的serial_port2,就不会出现错误提示。一旦想要通过任何方式将其指针赋值给在串口管理类中会有调用的serial_port,还是会出现前的错误提示。QT应该是通过某种预处理上的的魔法操作,判定在Serial_Thread的所有者中,会直接调用serial_port,于是规定serial_port必须留在主线程。

看来是没得偷懒了,必须对程序结构进行大改动。删除原先串口管理类中所有对于QSerialPort的直接调用,全部改为通过信号和槽进行调用。改了一个小时,终于将serial_port变为了Serial_Thread的私有成员,所有操作都通过线程间的消息进行传递。

头文件中:
class Serial_Thread : public QObject
{
    Q_OBJECT

public:
    explicit Serial_Thread(QWidget *parent = nullptr);
    ~Serial_Thread();

    bool isOpen() { return m_isOpen;}
    int error() { return m_error;}
    QString errorString() { return m_error_string;}


    QSerialPort* port() {return serial_port;}
signals:
    void signal_direct_incoming(QByteArray index,long sendtime = 0);   //子线程直接发出的信号,不受主线程的影响
public slots:
    void Slot_Send(QByteArray index);  //发送字符串
    void Slot_Clear();   //清空缓冲区

    void Init();
    void Slot_PortOpen(QSerialPortInfo info, int baudrate);
    void Slot_PortClose();
    void Slot_CheckError();
private slots:
    void Serial_Read();

private:
    QSerialPort* serial_port;
.....



class Serial_with_Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Serial_with_Dialog(QWidget *parent = nullptr, QString port_name  = "", QPushButton* button = nullptr);
    ~Serial_with_Dialog();


    Serial_Thread* serial_thread;
.....

signals:
    void signal_Log_Add(QString index);
    void signal_incoming(QByteArray index);  //通过界面转发的数据。这里通过了一层转发,速度会受到主线程工作强度的影响
    
    void signal_Init();  //在子线程中初始化
    void signal_PortOpen(QSerialPortInfo info, int baudrate);  //对子线程中的实体QSerialport进行操作
    void signal_PortClose();
    void signal_CherkError();
    void signal_Clear();
    void signal_Send(QByteArray index);

......



CPP文件中:

Serial_with_Dialog::Serial_with_Dialog(QWidget *parent, QString port_name, QPushButton *button) :
    QDialog(parent) ,
    ui(new Ui::Serial_with_Dialog)
{
    ui->setupUi(this);


    //Language_Change(); //切换语言
    serial_thread = new Serial_Thread(nullptr);
    QThread *thread_serial = new QThread(nullptr);   //为保证实时性,移动到子线程中进行调用
    serial_thread->moveToThread(thread_serial);
    QObject::connect(thread_serial, &QThread::finished, this, &QObject::deleteLater);      // 清理线程
    thread_serial->start(); // 开启线程

    QObject::connect(this, &Serial_with_Dialog::signal_Init, serial_thread, &Serial_Thread::Init,Qt::BlockingQueuedConnection); //连接发送函数
    signal_Init();

    QObject::connect(serial_thread, &Serial_Thread::signal_direct_incoming, this, &Serial_with_Dialog::Serial_Read,Qt::QueuedConnection); //连接读取函数
    QObject::connect(this, &Serial_with_Dialog::signal_Send, serial_thread, &Serial_Thread::Slot_Send,Qt::QueuedConnection); //连接发送函数
    QObject::connect(this, &Serial_with_Dialog::signal_Clear, serial_thread, &Serial_Thread::Slot_Clear,Qt::QueuedConnection); //连接发送函数

    QObject::connect(this, &Serial_with_Dialog::signal_PortOpen, serial_thread, &Serial_Thread::Slot_PortOpen,Qt::BlockingQueuedConnection);   //对串口进行操作的函数,需要等待操作结束
    QObject::connect(this, &Serial_with_Dialog::signal_PortClose, serial_thread, &Serial_Thread::Slot_PortClose,Qt::BlockingQueuedConnection);
    QObject::connect(this, &Serial_with_Dialog::signal_CherkError, serial_thread, &Serial_Thread::Slot_CheckError,Qt::BlockingQueuedConnection);
......




最终完成测试已经是两个小时以后了...这次终于达成了目的,初始化中不在出现前面的错误提示,QSerialPort完全在子线程中运行,即使主线程进入while(1)卡死,QSerialPort依旧可以正常响应外部输入的串口消息,并且与处于其他子线程中的类进行交互。

 

结论:

1. QObject: Cannot create children for a parent that is in a different thread 这个错误提示的实际意思是,不能在子线程中生成跨线程调用的成员。如果一个成员在父线程中被直接调用了,那么这个成员必须处在父线程中,强行在子线程中生成就会出现这个错误提示。

2. 要一个成员完全处于子线程中进行处理,则只能通过信号与其进行交互。

2. QT的预处理是真的有魔法

 

这个带有UI的串口管理类,在完善后会开源源代码。

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QObject: Cannot create children for a parent that is in a different thread 的相关文章

随机推荐

  • 解读CUDA Compiler Driver NVCC - Ch.3

    前言 上一篇文章简单了介绍了nvcc预定义的宏 以及支持的编译阶段 对应的输入文件后缀和输出文件的默认名 本篇文章了解CUDA源文件编译的整个workflow Overview CUDA编译的工作原理如下 输入程序经过设备编译编译预处理 编
  • HTTP的报文格式、GET和POST格式解析

    TTP报文是面向文本的 报文中的每一个字段都是一些ASCII码串 各个字段的长度是不确定的 HTTP有两类报文 请求报文和响应报文 请求报文 一个HTTP请求报文由请求行 request line 请求头部 header 空行和请求数据4个
  • JSON的下划线转驼峰,驼峰转下划线

    由于遇到了奇葩甲方 需要将数据格式转成下划线的格式 但是我们项目都是按照标准驼峰格式 所以写了个工具类来转换 不仅仅限于驼峰和下划线 根据需要传入 有没有大佬把这个递归改成迭代的 使用到的依赖 fastjon google的guava工具包
  • QT中代码设计和.ui文件设计的区别

    在面试中很多面试官经常会问到 ui和代码设计的区别 在网上一搜发现几乎没有人去解答这个问题 首先我们看一下一个简单的deamon 分别是代码实现和 ui实现 代码版 ui文件实现版 通过以上两种实现方式 不难发现 代码上的实现能够更精细 u
  • Navicat安装教程

    1 软件下载地址 点击下载 2 首先将下载后的文件解压到本地 3 右键选择以管理员身份运行navicat 15 0 64bit exe 4 然后点击下一步按钮 5 勾选我同意 然后点击下一步按钮 6 选择指定的安装目录 然后点击下一步按钮
  • micropython-SPI通讯

    micropython SPI通讯 1 什么是SPI 2 SPI通讯原理 3 Micropython中的SPI 4 ZTMR测试SPI 1 ZTMR中SPI引脚 2 ZTMRSPI自测 2 SPI 2板之间通讯测试 1 什么是SPI SPI
  • malloc底层原理实现

    使用过c语言的都知道malloc是一个动态分配内存的函数 还可以通过free释放内存空间 如果我们想分析一下malloc的源码 这其实不是一会就能看懂的 但是我们可以讨论一下malloc的简单实现 在这之前 我们先来看一下虚拟内存空间 虚拟
  • 错误until the update interval of nexus-releases has elapsed or updates are forced

    错误 until the update interval of nexus releases has elapsed or updates are forced 之前是先往nexues本地库里上传了jar文件 从文件系统里传的 而不是从ne
  • 解决Port 8080 is already in use

    前言 在运行项目的时候报错显示端口号已被占用 如下图 解决方法 第一步 win R打开 输入cmd进入命令窗口 第二步 输入netstat ano回车 找到对应的进程PID为14472 第三步 输入命令tasklist回车 找到对应的进程名
  • 面向对象和面向过程思想概述

    面向过程的思想去实现一个功能的步骤 首先要做什么 怎么做 最后我们再代码体现 一步一步去实现 而具体的每一步都需要我 们去实现和操作 这些步骤相互调用和协作 实现我们的功能 每一个步骤我们都是参与者 并且 需要面对具体的每一个步骤和过程 这
  • 猿人学2023比赛题1~7题解之第一题

    前言 我从不回头看之我跟77的雨后小故事 这题有两个解法 分别是全局扣和找加密魔改点套库 方法一 全局扣 1 加密位置 2 确保在浏览器出值跟浏览器一样 1 先把全部代码拉下来 整体运行 发现会缺东西 这是发包的 直接删了就好 2 然后加密
  • mysql联合for update查询_sql的for update

    欢迎大家吐槽 oracle行级共享锁 通常是通过select from for update语句添加的 同时该方法也是我们用来手工锁定某些记录的主要方法 比如 当我们在查询某些记录的过程中 不希望其他用户对查询的记录进行更新操作 则可以发出
  • 【导航】RT-Thread 学习专栏目录 【快速跳转】

    本文是 矜辰所致 的 RT Thread 记录专栏的内容导航 结合自己的学习应用过程的总结记录 有基础理论 有与FreeRTOS的比较 有实际项目 有应用总结 目录 前言 一 环境篇 二 内核篇 三 设备和驱动篇 四 组件软件包篇 五 应用
  • [异步][jms][activeMq]如何做到重试机制不会导致一条消息被多次执行.

    淘宝海量存储之单机事务面临的问题及解决办法 http blog csdn net jiao fuyou article details 15499261 这篇文章讲的比较好 核心关键词 幂等 Message Queue ActiveMQ r
  • 前端HTML鼠标经过链接变换背景颜色

  • Win10关闭自带键盘的三种方法--亲测第三中命令方式有效(需要重启)

    Win10笔记本关闭自带键盘的方法 方法一 1 在Windows10系统桌面 右键点击桌面上的 此电脑 图标 在弹出菜单中选择 属性 菜单项 2 在打开的Windows系统属性窗口中 点击左侧边栏的 设备管理器 菜单项 3 这时会打开设备管
  • 二叉树及其遍历

    二叉树的定义 二叉二叉顾名思义 二叉树是每个节点最多有两个子树的树结构 二叉树的存储 二叉树的存储分为顺序存储和链式存储 顺序存储 深度为k的二叉树需要预留2 k 1 个存储单元 按编号顺序存储 遇空节点留空位 可以看到上面特别多的空节点
  • spring cloud alibaba使用

    文章目录 架构图 环境搭建 Nacos 下载以及配置 测试使用 界面一些功能 可配置项 nacos自带的ribbon负载均衡 OpenFegin 日志配置 设置超时时间 自定义拦截器 Nacos config 根据nacos上的配置文件获取
  • Spring6 框架学习

    Spring6 框架学习 1 Spring介绍 1 1 简介 2002年 Rod Jahnson 首次推出了 Spring 框架雏形interface21 框架 2004年3月24日 Spring 框架以 interface21 框架为基础
  • QObject: Cannot create children for a parent that is in a different thread

    一篇又臭又长的流水账 要看结论可以直接拉到最后 在一个项目中 需要使用串口接收外部的对射管状态 然后调用传感器 由于在之前的项目中 自制了一个带有UI的串口管理类 继承QDialog 最早在主线程中生成这个串口管理类 但是发现程序变得越来越