Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用

Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用


目录

  • Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用
      • 1、问题描述
      • 2、Qt多线程的使用
      • 3、解决跨线程调用
        • 3.1 主线程调用子线程内QSerialPort的发送数据函数发数据
        • 3.2 主线程显示子线程内QSerialPort收到的数据
        • 3.3 实现代码
          • QWorker.h
          • QWorker.cpp
          • MainWindow.h
          • MainWindow.cpp
      • 4、综述


1、问题描述

相信很多使用Qt的朋友遇到过这个错误或者类似的错误:

Cannot create children for a parent that is in a different thread.

那么这个问题是怎么引起的呢?本篇文章以QSerialPort为例详细说说。

在我们的多线程编程中,一般主线程负责界面的刷新显示,而子线程负责一些耗时的操作,例如当我们使用QSerialPort的时候,我们希望QSerialPort的读写操作在子线程中进行。

那么如果在主线程中创建QSerialPort对象,通过指针的形式将QSerialPort传入到了子线程中,在子线程的函数内使用QSerialPort指针操作读写函数,就会出现上面的错误提示!

这就是跨线程调用引起的问题!

其实不止是QSerialPort,只要是QIODevice类型的对象都无法进行跨线程调用,这里是由于Qt的信号和槽机制的问题,再有就是QSerialPort是异步的,什么意思呢?就是说使用QSerialPort->readData()函数的时候不会阻塞,会立刻返回,其实QSerialPort->readData()只是在读取一个缓冲区而已,而在另一个线程中,有数据的话就会将数据写入到这个缓冲区。

2、Qt多线程的使用

Qt多线程有以下两种方式:

  1. 继承自QThread类,重写run函数,只有run函数内的代码是在子线程中
  2. 创建一个QThread和一个QWorker(继承自QObject)类对象,使用moveToThread函数移动到thread中运行,通过thread类start信号和worker槽函数绑定。此方法也是Qt官方推荐的办法。

具体代码实现可看我的另一篇博客:Qt实现多线程编程的两种方式

3、解决跨线程调用

既然无法通过指针的形式将QSerialPort传入子线程中,那么就只有在子线程中新建QSerialPort对象。

显然,如果使用上面第一种方式的话,由于只有run()函数内的代码是在子线程的,那么QSerialPort就只能在run函数中建立,如果在UI界面上有个点击发送数据的按钮的话,是无法将按钮点击事件传递到run()函数中的,显然,此种方法不合适。

3.1 主线程调用子线程内QSerialPort的发送数据函数发数据

如果想点击按钮发送数据的话,可以这么做:主线程点击按钮后emit一个信号,将这个信号与子线程中的槽连接,在槽函数中调用QSerialPort的发送数据函数。

3.2 主线程显示子线程内QSerialPort收到的数据

同样的,如果想将子线程里QSerialPort接收到的数据显示到UI界面上,可以在子线程QSerialPort收到数据后emit一个信号出来,将该信号和主线程里的槽函数连接,就实现了将数据传入到了主线程里,然后将数据显示到界面上就可以了。

tips:其实严格意义上来讲,Qt的多线程就不是使用继承QThread这种方式,而是moveToThread方式。

3.3 实现代码

说了这么多,下面就是具体代码的实现了。

QWorker.h

Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第1张图片

QWorker.cpp

Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第2张图片

MainWindow.h

Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第3张图片

MainWindow.cpp

Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第4张图片
Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第5张图片

主界面如下:
Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第6张图片

控制台看到打印信息,MainWindow的线程ID为:0x33a0,QWorker由于是在MainWindow实例化的,所以线程ID也是0x33a0。

接下来关闭应用程序,控制台打印如下:
Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第7张图片
从析构函数中的打印可以看到,此时QWorker的线程ID不在是0x33a0,说明我们使用worker->moveToThread(thread)确实是把worker移动到子线程内了。

重新启动应用程序,并点击打开串口按钮,控制台打印如下:
Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第8张图片
可以看到QWorker类的槽函数openPort显示的线程ID也是在子线程内,在看一下发送数据和接收数据:
Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第9张图片
都没问题!

4、综述

  1. Qt的QThread不是让你继承着写的,应该写个类用来操作QSerialPort,这里这个类假设就叫QWorker类。

  2. QWorker类成员里放一个QSerialPort指针,在QWorker构造函数中实例化QSerialPort,或者设置一个槽函数init,在init函数中实例化,然后将QThread的started信号和init槽函数连接。对外的操作全部用信号和槽。通过槽接收外部的调用操作,如open、close、write等;通过信号发送数据到外部,如收到的数据、错误码等。

  3. 程序退出时,不要直接delete QWorker,因为已经使用了moveToThread,此时子线程已经和MainWindow的线程ID不一样了。正确的操作方法应该是在moveTothread前,将thread的finished信号和槽函数deleteLater相连接,以及将thread的finished和worker的槽函数deleteLater相连接,这样worker就会由所属的线程负责释放资源,此时程序的销毁流程就是:子线程删除worker对象->子线程停止->子线程对象销毁。如下图的代码:
    Qt | QSerialPort、QTcpSocket等QIODevice设备的跨线程调用报错解决办法以及多线程的使用_第10张图片

  4. 线程对象,以及move到线程里的对象,都不要设置parent。如本例中的thread和worker。


另外推荐一下这位知乎网友写的回答:https://www.zhihu.com/question/31518679/answer/130770474


至此,完!

你可能感兴趣的:(Qt,qt,跨线程调用,QSerialPort,多线程,QTcpSocket)