QT 多线程中使用QCanBusDevice进行PCAN通讯时,无法正常发出数据

news2025/9/15 1:52:08

QT 多线程中使用QCanBusDevice进行PCAN通讯时,无法正常发出数据

前言

我一开始的代码逻辑是,PCAN开启、关闭、发送、接收这些功能整合在一个工具类中,这个工具类的对象是在主线程创建的,然后我有一个要循环定时发送的功能是独立的一个线程,我在这个线程调用发送函数时,虽然返回的都是true,但是抓报文后却发现没有发出来。

出错代码结构(模拟)

/* 打开PCAN函数 */
void MainWindow::on_btn_open_clicked()
{
	...
	m_canDevice=QCanBus::instance()->createDevice(...);
	/* PCAN启动成功执行 */
	if (m_canDevice)
	{
	  	...
		canOpened = true; /* PCAN开启标志位 */
		/* 创建定时循环发送线程 */
		QFuture<void> thread = QtConcurrent::run(this, thread_send_data); 
	}
}
/* 定时循环发送函数 */
void MainWindow::thread_send_data()
{
    while (canOpened)
    {
        send_data();			/* 直接调用发送函数 */
        QThread::msleep(1000);  /* 定时1秒发送一次 */
    }
}
/* 发送函数,由于是Demo,所以是写死的 */
void MainWindow::send_data()
{
    QCanBusFrame frame;
    QByteArray data;
    data.resize(8);
    data[0]= 0x70;
    data[1]= 0xFF;
    data[2]= 0xFF;
    data[3]= 0xFF;
    data[4]= 0xFF;
    data[5]= 0xFF;
    data[6]= 0xFF;
    data[7]= 0xFF;
    quint32 ID = 0x18510004;
    frame.setFrameType(QCanBusFrame::DataFrame);
    frame.setExtendedFrameFormat(ID > 0x7FF ? true : false); /* 是否为扩展帧 */
    frame.setTimeStamp(QDateTime::currentMSecsSinceEpoch());
    frame.setFrameId(ID);
    frame.setPayload(data);
    /* m_canDevice是主线程中创建的QCanBusDevice对象 */
    if (m_canDevice->writeFrame(frame))
    {
        qDebug()<<"发送成功";
        return;
    }
    qDebug()<<"发送失败";
}

原因

在QT编辑器的控制台中,出现了以下错误提示:

QObject::startTimer: Timers cannot be started from another thread

可以明显看出,它说定时器不能在其他线程开启,说明发送函数的底层用到了定时器,且需要将发送函数在对象的同一线程中执行。
我之前是直接调用的,因为QCanBusDevice对象是在主线程中创建的,所以在其他线程中用这个对象的发送函数就出错了,这里就可以使用信号与槽机制,把发送这个步骤切换到主线程中执行。

解决方法

创建一个信号:

void sendData();

连接信号与槽,把发送信号和发送函数连接起来:

connect(this, &MainWindow::sendData, this, &MainWindow::send_data);

修改定时循环函数:

void MainWindow::thread_send_data()
{
    while (canOpened)
    {
        emit sendData();		/* 直接调用改为发送信号 */
        QThread::msleep(1000);
    }
}

深究

那么为什么使用了信号与槽后,问题就解决了呢?
我们可以认定,使用了信号与槽后,发送函数是在主线程中执行的,其实connect函数还有第五个参数,这个参数可以设置槽函数同步还是异步,甚至在哪一方执行。
在这里插入图片描述
Qt::ConnectionType
参考博客:https://blog.csdn.net/weixin_41761608/article/details/119237172

类型功能
Qt::AutoConnection默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
Qt::DirectConnection槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。
Qt::QueuedConnection槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
Qt::BlockingQueuedConnection槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
Qt::UniqueConnection这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

如果我们要知道是否发送成功,就可以把发送函数和发送信号的类型改为bool,发送成功返回true,发送失败返回false,而连接的类型应使用Qt::BlockingQueuedConnection,如果用默认或者Qt::QueuedConnection都无法获取到发送的状态,,一直为false,因为不是同步的。

最终代码

signals:
	bool sendData();
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(this, &MainWindow::sendData, this, &MainWindow::send_data, Qt::BlockingQueuedConnection);
}
/* 打开PCAN函数 */
void MainWindow::on_btn_open_clicked()
{
	...
	m_canDevice=QCanBus::instance()->createDevice(...);
	/* PCAN启动成功执行 */
	if (m_canDevice)
	{
	  	...
		canOpened = true; /* PCAN开启标志位 */
		/* 创建定时循环发送线程 */
		QFuture<void> thread = QtConcurrent::run(this, thread_send_data); 
	}
}
/* 定时循环发送函数 */
void MainWindow::thread_send_data()
{
    while (canOpened)
    {
    	/* 发送失败执行 */
        if (!sendData())
        {
			...
		}
        QThread::msleep(1000);  /* 定时1秒发送一次 */
    }
}
/* 发送函数,由于是Demo,所以是写死的 */
bool MainWindow::send_data()
{
    QCanBusFrame frame;
    QByteArray data;
    data.resize(8);
    data[0]= 0x70;
    data[1]= 0xFF;
    data[2]= 0xFF;
    data[3]= 0xFF;
    data[4]= 0xFF;
    data[5]= 0xFF;
    data[6]= 0xFF;
    data[7]= 0xFF;
    quint32 ID = 0x18510004;
    frame.setFrameType(QCanBusFrame::DataFrame);
    frame.setExtendedFrameFormat(ID > 0x7FF ? true : false); /* 是否为扩展帧 */
    frame.setTimeStamp(QDateTime::currentMSecsSinceEpoch());
    frame.setFrameId(ID);
    frame.setPayload(data);
    /* m_canDevice是主线程中创建的QCanBusDevice对象 */
    return m_canDevice->writeFrame(frame);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/117145.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

与企企通强强联手!哈尔斯二期数字化采购项目正式启动

近日&#xff0c;浙江哈尔斯真空器皿股份有限公司&#xff08;以下简称“哈尔斯”&#xff09;联合企企通举办二期数字化采购项目启动会&#xff0c;旨在助力哈尔斯实现采购数字化全面升级&#xff0c;提升自主品牌竞争力。会上&#xff0c;双方就该项目的建设方案、项目资源、…

铝合金表面处理废水除铝工艺

铝型材表面处理用水量大&#xff0c;产生废水多&#xff0c;废水中有害物质持续排放。如不加以处理必将污染环境。同时伴随着我国对排污量的征税&#xff0c;也会增加企业的成本和负担。因此&#xff0c;从企业的社会责任和效益两方面考虑&#xff0c;进行废水处理是必须和必要…

解决VsCode启动Vue项目报错:‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

问题描述 最近居家办公&#xff0c;网速不太稳定&#xff0c;开会的时候网络也是断断续续的&#xff0c;今天需要拉下前端项目运行起来 在我执行npm i下载包的时候&#xff0c;我看到网络超时的错误警告就感觉不太秒。知道大概率要启动失败了 果不其然执行npm run serve的时…

窃取信息的新恶意软件通过假冒的破解网站感染使用者

©网络研究院 一种名为“RisePro”的新型信息窃取恶意软件正在通过由 PrivateLoader 按安装付费 (PPI) 恶意软件分发服务运营的虚假破解站点进行分发。 RisePro 旨在帮助攻击者从受感染的设备中窃取受害者的信用卡、密码和加密钱包。 本周Flashpoint 和 Sekoia的分析师发…

前端框架 Nuxt3 集成 Pinia

目录 一、Nuxt3集成Pinia 二、Pinia的使用 state的使用 1、基本使用及动态渲染 2、state的重置 3、批量更改state数据 getters的使用 1、getters的基本使用 2、getters传参 actions的使用 1、actions的基本使用 一、Nuxt3集成Pinia 参考官方文档&#xff1a;简介 |…

【JavaSE】常用类(447~515)

String 447.常用类-每天一考 1.画图说明线程的生命周期&#xff0c;以及各状态切换使用到的方法等 状态&#xff0c;方法 2.同步代码块中涉及到同步监视器和共享数据&#xff0c;谈谈你对同步监视器和共享数据的理解&#xff0c;以及注意点。 synchronized(同步监视器){//操…

消息队列RabbitMQ学习笔记(五)高级特性

1. 发布确认高级 在生产环境中由于一些不明原因&#xff0c;导致 RabbitMQ 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败&#xff0c; 导致消息丢失&#xff0c;需要手动处理和恢复。于是&#xff0c;我们开始思考&#xff0c;如何才能进行 RabbitMQ 的消息可靠投…

ccc-sklearn-11-线性回归(1)

1.线性回归概述 回归需求在现实中非常多&#xff0c;自然也有了各种回归算法。最著名的就是线性回归和逻辑回归&#xff0c;衍生出了岭回归、Lasso、弹性网&#xff0c;以及分类算法改进后的回归&#xff0c;如回归树、随机森林回归、支持向量回归等&#xff0c;一切基于特征预…

自定义卷积实现卷积的重参数【手撕代码】

在我的上篇文章中主要对RepVGG进行了解析【RepVGG网络中重参化网络结构解读】&#xff0c;里面详细的对论文中的代码进行了解析&#xff0c;展示了RepVGG在重参数时是如何将训练分支进行合并的&#xff0c;总的一句话就是在推理阶段&#xff0c;会将1x1以及identity分支以paddi…

vivo 游戏中心低代码平台的提效秘诀

作者&#xff1a;vivo 互联网服务器团队- Chen Wenyang 本文根据陈文洋老师在“2022 vivo开发者大会"现场演讲内容整理而成。公众号回复【2022 VDC】获取互联网技术分会场议题相关资料。 在互联网流量见顶和用户需求分层的背景下&#xff0c;如何快速迭代产品功能&#xf…

函数模板-C11/17/14

函数模板 文章目录函数模板定义函数模板使用函数模板样例两阶段翻译 Two-Phase Translation模板的编译和链接问题多模板参数引入额外模板参数作为返回值类型让编译器自己找出返回值类型将返回值声明为两个模板参数的公共类型样例默认模板参数样例重载函数模板模板函数特化非类型…

cocoapods的使用

swift开发之cocoapods的使用 之前介绍了cocoapods的使用&#xff0c;我们可以知道通过pod search XXX(三方依赖库名称)可以就搜索到想要的第三方是否存在。 这次主要简单介绍cocoapods如何引入第三方库的,以BluetoothKit为例。 首先&#xff0c;我们终端中通过cd命令定位到要…

二十二、shiro安全框架基础

一、简介 1. shiro简介 Apache Shiro 是 Java 的一个安全&#xff08;权限&#xff09;框架。Shiro 可以非常容易的开发出足够好的应用&#xff0c;其不仅可以用在JavaSE 环境&#xff0c;也可以用在 JavaEE 环境。Shiro 可以完成&#xff1a;认证、授权、加密、会话管理、与…

“智慧”控漏 削减产销差-城镇供水管网分区计量管理系统

平升电子城镇供水管网分区计量管理系统根据国际国内分区计量的要求和标准研发&#xff0c;专门针对水司漏损控制和产销差管理而设计。系统涵盖分区管理、管网流量和压力监控、水量统计分析、产销差分析、漏损评估、夜间最小流量分析、用水异常报警等功能。核心目标是找到整个管…

ReactJS入门

目录 一&#xff1a;前端开发的演变 二&#xff1a;ReactJS简介 三&#xff1a;搭建环境 四&#xff1a;React快速入门 一&#xff1a;前端开发的演变 到目前为止&#xff0c;前端的开发经历了四个阶段&#xff0c;目前处于第四个阶段。这四个阶段分别是&#xff1a; 阶段一…

equals()与hashcode()之间的关系

1、equals简介 被用来检测两个对象是否相等&#xff0c;即两个对象的内容是否相等&#xff1b; equals 方法&#xff08;是String类从它的超类Object中继承的&#xff09;用于比较引用和比较基本数据类型时具有不同的功能&#xff1a; 比较基本数据类型&#xff0c;如果两个值…

马哥SRE第11周课程作业

ansible role zabbix相关话题1. ansible 常用指令总结&#xff0c;并附有相关示例。1.1 Ansible相关工具1.1.1 ansible-doc1.1.2 ansible 命令用法1.1.3 ansible-console1.1.4 ansible-playbook1.1.5 ansible-vault1.1.5 ansible-galaxy2. 总结ansible playbook目录结构及文件用…

javaee之Spring4

之前说到AccountDao需要继承JdbcDaoSupport这个类&#xff0c;那么现在来看一下这个类的内容 JdbcDaoSupport.java package com.itheima.dao.impl;/*** 此类用于抽取dao中的重复代码 */public class JdbcDaoSupport {private JdbcTemplate jdbcTemplate;public void setJdbcT…

人大金仓数据库备份应用sys_dump的使用

人大金仓数据库软件给数据库管理员用户提供了管理维护数据库的多个客户端应用&#xff0c;更多参考&#xff1a;《KingbaseES客户端应用参考手册》。 我们可以看到备份的应用有两个&#xff1a; 1、sys_dump:将KingbaseES数据库备份为一个脚本文件或者其他归档文件 2、sys_d…

表单校验重要性和多规则校验

表单校验分类 校验位置&#xff1a; 客户端校验 服务端校验 表单校验框架 JSR&#xff1a;java规范提案 303&#xff1a;提供bean属性相关校验规则 JCP:java社区 Hibernate框架中包含一套独立的校验框架hibernate-validator 实际的校验规则 同一个字段有多个约束条件 引用…