QThread中调用QSerialPort::waitForReadyRead()无法读取数据求助
问题原因及解决方案
这个问题我之前踩过坑,核心原因是Qt里QObject的线程亲和性在作祟!
为什么读不到数据?
QSerialPort是QObject的子类,所有QObject对象都有一个“线程亲和性”——它只能在创建它的线程中安全地调用成员函数。你在MainWindow(主线程)中创建了serial对象,并且把它的指针传给了子线程WorkerThread,然后在子线程里调用thread_serial->waitForReadyRead(),这完全违反了Qt的线程模型:
- QSerialPort的I/O操作依赖于它所在线程的事件循环,子线程无法触发主线程中串口对象的事件处理逻辑;
- 跨线程直接调用QObject的非线程安全成员函数,行为是未定义的,大概率会出现各种异常(比如读不到数据、程序崩溃)。
另外你代码里还有个小细节:串口名写的是"COM 4"(带空格),Windows系统下正确的串口名应该是"COM4",这个也可能导致串口连接的隐性问题。
解决方案
这里提供两种靠谱的解决思路,根据你的需求选择:
方案1:把串口的创建、打开都移到子线程中
让串口对象的线程亲和性直接属于子线程,这样在子线程里调用阻塞读取函数就完全合法了:
修改WorkerThread类:
class WorkerThread : public QThread { Q_OBJECT public: void run() override { // 在子线程中创建串口对象,不要设置parent QSerialPort *thread_serial = new QSerialPort(); thread_serial->setPortName("COM4"); thread_serial->setBaudRate(QSerialPort::Baud115200); thread_serial->setDataBits(QSerialPort::Data8); thread_serial->setParity(QSerialPort::NoParity); thread_serial->setStopBits(QSerialPort::OneStop); thread_serial->setFlowControl(QSerialPort::NoFlowControl); if (thread_serial->open(QIODevice::ReadWrite)) { qDebug()<<"Serial port opened in worker thread"; // 现在可以安全调用阻塞读取函数 int ret = thread_serial->waitForReadyRead(60000); if (ret) { qDebug()<<"Data received"; QByteArray recvData = thread_serial->readAll(); qDebug()<<recvData; emit progressChanged(); } thread_serial->close(); } else { qDebug()<<"Failed to open serial port in thread"; } delete thread_serial; } signals: void progressChanged(); };
然后修改MainWindow的openSerialPort函数,不用在主线程创建串口了:
void MainWindow::openSerialPort( ) { qDebug()<<"Starting worker thread"; WorkerThread *workerThread = new WorkerThread(); connect(workerThread, SIGNAL(progressChanged()), this, SLOT(onProgressChanged())); // 线程结束后自动清理 connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); }
方案2:用信号槽异步传递数据(更符合Qt的事件驱动模型)
保持串口在主线程中,通过readyRead信号触发数据读取,再把数据传递给子线程处理,避免阻塞操作:
修改WorkerThread类,添加处理数据的槽函数:
class WorkerThread : public QThread { Q_OBJECT public slots: // 子线程中处理数据的槽函数 void processSerialData(const QByteArray &data) { qDebug()<<"Data received in thread:"<<data; emit progressChanged(); } protected: void run() override { // 启动子线程的事件循环,才能接收槽函数调用 exec(); } signals: void progressChanged(); };
修改MainWindow的openSerialPort函数,连接串口的readyRead信号:
void MainWindow::openSerialPort( ) { serial = new QSerialPort(this); serial->setPortName("COM4"); serial->setBaudRate(QSerialPort::Baud115200); serial->setDataBits(QSerialPort::Data8); serial->setParity(QSerialPort::NoParity); serial->setStopBits(QSerialPort::OneStop); serial->setFlowControl(QSerialPort::NoFlowControl); if (serial->open(QIODevice::ReadWrite)) { qDebug()<<"Connected"; WorkerThread *workerThread = new WorkerThread(); // 串口收到数据时,读取并传给子线程处理 connect(serial, &QSerialPort::readyRead, this, [this, workerThread](){ QByteArray data = serial->readAll(); // 用invokeMethod跨线程调用子线程的槽函数 QMetaObject::invokeMethod(workerThread, "processSerialData", Qt::QueuedConnection, Q_ARG(QByteArray, data)); }); connect(workerThread, SIGNAL(progressChanged()), this, SLOT(onProgressChanged())); connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); } }
关键注意事项
- 永远不要跨线程直接调用QObject的非线程安全成员函数,QSerialPort的所有I/O相关函数都不是线程安全的;
- 如果一定要把已创建的QObject移到其他线程,必须确保:
- 该对象没有父对象;
- 在移动前没有调用过任何可能触发事件循环的函数;
- 使用
moveToThread()方法完成线程迁移。
内容的提问来源于stack exchange,提问作者Sreejith
相关产品推荐
相关产品推荐

