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:关于事件触发的子线程方法后续补充