zoukankan      html  css  js  c++  java
  • 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();退出消息循环后会自动退出线程。

  • 相关阅读:
    Android 六种核心安全机制
    Android 网络通信 HTTP
    Android Thread和AsyncTask
    C#(少用的)
    Asp.net动态生成表单
    设计模式--职责链(学习)
    Extjs表单验证小结
    C#框架
    Javascript获取IFrame内容(兼容IE&FF)
    最近在忙淘宝店的事
  • 原文地址:https://www.cnblogs.com/judes/p/6884964.html
Copyright © 2011-2022 走看看