????????記錄一下自己使用多線程進行串口管理和數據讀取的過程。如果有問題的話可以發消息給我。
背景
在使用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:關于事件觸發的子線程方法后續補充