QT线程

一、QObject子类

说明:以串口线程传输文件为例子,使用的是MoveTothread函数。

void QObject::moveToThread(QThread *targetThread)可以将QObject里面的所有事件都会被放在targetThread线程中执行。

如果希望某个对象在线程中做某事,那么这个对象也应该在此线程中创建,如果在主线程中创建将不能在子线程中执行。

所以如果想在子线程中操作串口对象进行文件的读写,也需要在子线程中创建串口对象。

1、新建类继承QObject

右键项目添加新文件,选择C++类,选择基类为QObject,设置类名为Serial

2、在Serial头文件中声明串口对象

QSerialPort* seriaport;

3、在Serial中声明并实现打开串口函数、发送文件函数、接收文件函数、关闭串口函数这些槽函数

4、在主线程头文件中声明此类和一个子线程

Serial* serial;

QThread *serialThread ;

5、在主线程构造函数中new出对象

this->serial = new Serial();

this->serialThread = new QThread();

6、在主线程构造函数中将类放进线程里

serial->moveToThread(serialThread);

serialThread.start();

7、在主线程中声明打开串口、发送文件、接收文件、关闭串口这些信号

8、在主线程中发送对应信号,触发Serial里的槽函数,这些槽函数将在子线程中执行。

查看当前线程ID:

qDebug()<<"id = "<<QThread::currentThreadId();

 二、QThread子类

1、新建类MyThread继承QThread,重写run()函数

Q_OBJECT//头文件中加了这个宏才能使用信号与槽

void run();//.cpp中实现

  while(bool flag){}

}

2、在MainWindow中定义MyThread对象

#include "MyThread.h"

MyThread* myThread = new MyThread();

3、启动线程

myThread->start();

4、停止线程

myThread->quit();

三、两种方式结合

结合两种方式,我有以下理解:

第一种方式适用于有信号发出,直接调用对应QObject中的槽函数即可,每个槽函数适用于单独做一件事情比如串口的发送数据(而不适合做一个循环)。

如果有多个信号对应这些槽函数,只需要connect起来即可,这些槽函数都会在线程中做,很方便;

第二种适用于等待某个回调事件,比如周立功can通信时的接收数据是没有信号发出的(毕竟dll不是qt下做的),只有一个读缓冲区有多少数据的函数,

所以可以在run中死循环观察缓冲区是否有数据,从而回调接收数据。

看了很多文章,都单独对两个方式做了很好的介绍。其实QThread也是有movetothread函数的,说明也可以将QThread子类中的槽函数放在另一个线程中使用,那这样就可以将两种方式结合起来。

首先假设主线程id是0x0001,QThread子类myThread本身是个线程的入口(run函数),所以它自己可以看做是个线程,假设id是0x0002,可以将处理函数放在run里面,在主线程中start、quit就可以控制run的开启和停止;(第二种)

然后在QThread中定义n个槽函数,再在主线程中new一个QThread对象lastThread,假设id是0x0003,然后把myThread.movetothread(lastThread),此时当触发这n个槽函数时,他们都在0x0003中运行的。(第一种)

这个方式是在使用周立功can api写can通信时实现的,大致方式如下:

1、新建QThread子类CanThread,重写run,在run中死循环接收文件;

2、在CanThread中声明定义一个发送数据槽函数sendCAN();

3、在主线程中开启CanThread,canThread.start();接收数据;

4、在主线程中new一个QThread对象lastThread,转移槽函数到此对象,canThread.moveToThread(lastThread);

5、在主线程中需要发送数据时,发出信号控制sendCAN就行了,此时run是在本身线程中执行的,sendCAN是在lastThread中执行的。

ps:http://blog.csdn.net/life_is_too_hard/article/details/52089723

Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。

Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。

Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

PS:

1、在主线程中,做其他类的构造函数,构造函数中的代码是在主线程中做的。所以QThread子类的构造函数中不要初始化希望在线程中做的对象(如udp、串口对象,不要在QThread的构造函数中new)

2、在QThread中new对象(如udp、串口对象,不要传this进去,具体原因后面再说)

3、最好在主线程的构造函数里为子线程分配空间和连接信号槽,在主线程的触发函数里开启关闭线程。(多次连接信号槽,会导致多次发送此信号并且多次执行槽函数

4、线程高阶(总结)

正确退出线程的唯一方式:设置标志位(而不是quit之类的)


QThread::exec()进入事件循环
QThread::quit()、exit(int)停止事件循环[quit()==exit(0)]

QThread只是告诉操作系统一个线程的入口是run,本身还是属于主线程的对象

除了线程run()里面的代码,其他都是在依附线程中运行的(主线程中的QThread对象依附主线程)

 

QThread有两种类型:
1、有消息循环。直接就是QThread实例化的子类。这样直接实例化QThread对象不会重写run(),由于run默认调用exec()来进入消息循环
QThread tempThread;
或者
QThread *tempThread = new QThread();
此类线程对象可以用来承载其他对象的槽函数:QObject::moveToThread(&QThread)退出消息循环只需要quit()、exit()。
2、没有消息循环。想要去除消息循环,定义子类继承QThread,重写run(),注意不要在里面加上exec()。
class MyThread: public QThread{
protected:
void run()
{while(flag){do}}
};
此类线程用于循环做某个事情,比如接收数据,退出此线程的方式是flag=false;

高速CAN总线设计方案:(达到1ms)
由于CAN总线数据的传输不依赖于某一对象(如串口、TCP/IP都需要特定的对象来作为载体传输数据),所以比较特殊,CAN设备的初始化、回收等可以在主线程中完成,只需要在线程中做数据传输(串口、TCP/IP对象的初始化、回收也需要在线程里做)
一、在主线程中打开、关闭CAN
二、接收解析
1、为了保证接收数据正确性,自定义QThread子类,在run里while循环接收数据,此线程子类只做数据接收。接收到数据后发送给2中的QObject子类。
2、自定义QObject子类,定义QTimer,20ms(按照情况自定义)做一个定时器槽函数;定义全局变量;然后使用moveToThread将此子类槽函数移动到有消息循环的QThread对象中。
QString allInfo;//总的信息,每解析一个obj就把信息追加到allInfo中
QString rcvInfoNew[20];//每个报文具体的解析数据,这是解析的当前的
QString rcvInfoOld[20];//然后在定时器槽函数中判断rcvInfoNew[i]==rcvInfoOld[i];如果相等则不做处理,如果不相等则发送给主界面解析框显示,并把old=new
PS:解析方法应放在自定义方法类中,作为静态函数使用
3、主界面接收特定信号显示
三、发送
1、在上面的QObject子类中定义发送槽函数和定时器QTimer,主线程通过调用对象函数将obj和循环发送的间隔时间穿进去。
PVCI_CAN_OBJ obj[100];int objNum=0;
void getSendInfo(PVCI_CAN_OBJ obj,int objNum,int time)//又主线程调用并设置需要发送的obj和间隔时间
{
1、获取obj;
2、获取obj个数;
3、获取间隔时间
4、连接定时器信号槽,当时间到了就执行发送槽函数
5、QTimer::start();
}
void timerOutSlot()//定时器槽函数
{
VCI_Trasmit();
}

PS:退出死循环接收线程,是使标志位为false;退出接收解析和发送,都是调用依附线程的quit();退出消息循环后会自动退出线程。

原文地址:https://www.cnblogs.com/judes/p/6884964.html