当前位置: 首页 > ops >正文

QT 多线程 管理串口

        记录一下自己使用多线程进行串口管理和数据读取的过程。如果有问题的话可以发消息给我。

背景

在使用QT制作一个串口数据读取处理的小软件的时候,发现了存在界面卡顿的情况,感觉性能太低,于是考虑把串口数据的读取和处理都放到子线程的缓冲区中,然后等到主线程需要的时候来使用。

设计

一开始使用了继承子QThread 的自定义worker类来进行管理,发现很不方便,还有各种安全问题,于是改用了只自定义worker类,然后在主线程中使用QThread,然后直接moveToThread的方法。

Worker类

class EMGWorker : public QObject
{Q_OBJECT
private:QSerialPort *m_serialPort;          // 串口指针QTimer *m_readTimer;                // 数据读取定时器QQueue<EMGDataFrame> m_emgBuffer;   // EMG数据缓冲区mutable QMutex m_bufferMutex;       // 缓冲区互斥锁static const int MAX_BUFFER_SIZE = 5000;static const int READ_INTERVAL_MS = 10;  // 10ms读取间隔public slots:void initialize(const QString &portName, int baudRate = 115200);void cleanup();void startReadingInThread();void stopReadingInThread();private slots:void readEMGData();  // 定时器触发的数据读取void onSerialError(QSerialPort::SerialPortError error);
};

大概设计如上,在UI中有打开串口的按钮来控制什么时候传递串口相关参数,还有搜集数据按钮来控制什么时候打开定时器进行读取。

初始化

主要内容就是等待打开串口按钮的事件触发之后才初始化Worker相关内容,注意定时器要在对应的工作线程中创建。

void EMGWorker::initialize(const QString &portName, int baudRate)
{// 在工作线程中创建串口m_serialPort = new QSerialPort(this);// 设置串口参数m_serialPort->setPortName(portName);m_serialPort->setBaudRate(baudRate);m_serialPort->setDataBits(QSerialPort::Data8);m_serialPort->setParity(QSerialPort::NoParity);m_serialPort->setStopBits(QSerialPort::OneStop);m_serialPort->setFlowControl(QSerialPort::NoFlowControl);// 连接串口信号connect(m_serialPort, &QSerialPort::errorOccurred,this, &EMGWorker::onSerialError);// 创建定时器(定时读取模式)m_readTimer = new QTimer(this);m_readTimer->setInterval(READ_INTERVAL_MS);connect(m_readTimer, &QTimer::timeout, this, &EMGWorker::readEMGData);// 打开串口if (m_serialPort->open(QIODevice::ReadWrite)) {emit connected();} else {emit errorOccurred("EMG串口打开失败: " + m_serialPort->errorString());}
}
线程间通信

在Qt中有一个重要规则:QObject及其子类的方法必须在创建该对象的线程中调用。

// 使用QMetaObject::invokeMethod实现线程安全调用
void EMGWorker::startReading()
{// Qt::QueuedConnection确保方法在目标对象所在的线程中异步执行QMetaObject::invokeMethod(this, "startReadingInThread", Qt::QueuedConnection);
}void EMGWorker::stopReading()
{QMetaObject::invokeMethod(this, "stopReadingInThread", Qt::QueuedConnection);
}

MainWindow控制

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{// 1. 创建EMG数据线程m_emgThread = new QThread(this);m_emgWorker = new EMGWorker();m_emgWorker->moveToThread(m_emgThread);// 2. 连接EMG线程信号connect(m_emgWorker, &EMGWorker::errorOccurred, this, &MainWindow::onEMGWorkerError);connect(m_emgThread, &QThread::finished, m_emgWorker, &EMGWorker::cleanup);
}

MainWindow析构

MainWindow::~MainWindow()
{isCollecting = false;// 断开所有信号连接if (m_emgWorker) {disconnect(m_emgWorker, nullptr, this, nullptr);m_emgWorker->stopReading();}// 等待线程结束if (m_emgThread && m_emgThread->isRunning()) {m_emgThread->quit();m_emgThread->wait(3000);}
}

ps:关于事件触发的子线程方法后续补充

http://www.xdnf.cn/news/15285.html

相关文章:

  • Node.js特训专栏-实战进阶:16. RBAC权限模型设计
  • 沃尔玛 卡号查询 滑块 分析
  • 深度学习图像分类数据集—角膜溃疡识别分类
  • TensorFlow深度学习实战(24)——变分自编码器详解与实现
  • spring-ai-alibaba 1.0.0.2 学习(十六)——多模态
  • IP 地址与网络基础全面解析
  • ARC 02 runner scale set chart:对接集群与 Github Action 服务器
  • 在 OCI 生成式 AI 上搭一个「指定地区拉面店 MCP Server」——从 0 到 1 实战记录
  • 基于SpringBoot3集成Kafka集群
  • CSS个人笔记分享【仅供学习交流】
  • Utils系列之内存池(MultiSizePool)
  • 电商系统未来三年趋势:体验升级、技术赋能与模式重构
  • 关于ISO 26262的Single-Point Fault/Residual Fault/Latent Fault/Dual-Point Fault的整理
  • Android 响应式编程完整指南:StateFlow、SharedFlow、LiveData 详解
  • Docker 基于 Cgroups 实现资源限制详解【实战+源码】
  • CAU数据挖掘第四章 分类问题
  • Linux修炼:开发工具
  • 软件开发中的瀑布式开发与敏捷开发
  • 2025湖北省信息安全管理与评估赛项一阶段技能书
  • 在 JetBrains 系列 IDE(如 IntelliJ IDEA、PyCharm 等)中如何新建一个 PlantUML 文件
  • 新手向:使用Python构建高效的日志处理系统
  • Llama系列:Llama1, Llama2,Llama3内容概述
  • Web攻防-PHP反序列化魔术方法触发条件POP链构造变量属性修改黑白盒角度
  • Python爬虫实战:研究xlwings库相关技术
  • Qt 3D模块加载复杂模型
  • CA复习功课
  • 前端进阶之路-从传统前端到VUE-JS(第五期-路由应用)
  • react中为啥使用剪头函数
  • 【Java入门到精通】(三)Java基础语法(下)
  • 博途多重背景、参数实例--(二)