QT之深入理解QThread

 
    理解QThread之前需要了解下QThread类,QThread拥有的资源如下(摘录于QT 5.1 帮助文档):
 
    在以上资源中,本文重点关注槽:start();信号:started()、finished();受保护的方法:run()、exec();
 
理解QThread
    QThread与通常所熟知的线程(thread)有很大出入,在面向过程的语言中,我们建立一个线程的同时会传入一个函数名,这个函数名代表该线程要执行的具体代码(如图 1 所示)。
图 1. 我们通常所理解的线程
    但是QThread里并没有线程的具体代码,QThread只是一个接口而已,目的是为操作系统提供一个用于线程调度的“句柄”。这个“句柄”即是QThread的入口(如图 2 所示)。
图 2. QThread是“面向对象的”
    QThread的入口多种多样,可以是槽函数,也可能是某个事件处理函数,但是由于是由系统调度的,因此这些函数的“准确”执行时刻是无法预知的。
    QThread的出口是finished()信号。
    作为线程,QThread会毫不犹豫的为自己创建一个运行空间,一个单独的执行线索,一个新的线程,但是翻阅QThread所拥有的资源,我们找不到传入函数名的地方,因此我们仿佛无法为这个新创建的线程提供具体的执行代码。
    很多人因此想到了run()方法,因而继承QThread函数,并将自己的代码写在run()方法中,往往要求run()方法不可以立刻退出,因此加入循环体和wait()方法,有时候为了响应事件而调用exec()进行堵塞。但这种做法是不建议的,已有文章指出“QThread was designed and is intended to be used as an interface or a control point to an operating system thread, not as a place to put code that you want to run in a thread. ”具体参见:<http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/>。
    那么,QThread真的不能执行具体代码么?如果不是,怎样将要在新线程中执行的程序交付给QThread呢?答案是moveToThread()方法。任何基于QObject类的子类都具有该方法。某个对象被moveTo到新线程后,它所具有的槽函数和事件处理程序都会被移动到新线程所在的运行空间中,成为新线程与操作系统之间的接口,即成为了新线程的入口。当有与这个槽连接的信号或与之相配的事件发生时,槽函数和事件处理程序将会在新线程空间中执行。
    如果只到此为止,那么很容易出现另一个问题,也就是上面连接中所举的例子。我们在这里详细说明。程序如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyThread : public QThread
{
public:
    MyThread()
    {
        moveToThread(this);
    }
 
    void run();
 
signals:
    void progress(int);
    void dataReady(QByteArray);
 
public slots:
    void doWork();
    void timeoutHandler();
};
    上面这段程序的问题在哪儿呢?正如原文所说:“We’re telling the thread to run code “in itself”.We’re also doing this before the thread is running as well. Even though this seems to work, it’s confusing, and not how QThread was designed to be used (all of the functions in QThread were written and intended to be called from the creating thread, not the thread that QThread starts).”
    总结起来,问题有两点:1.在构造函数中moveToThread(),此时MyThread还没有开始运行;2.将MyThread移动到它自己空间去运行后,我们失去了对MyThread的引用。以上两点都容易导致非常致命的问题。可见,我们为了让代码在新线程中得以执行,我们实在有点儿太“不择手段”了。
    出现以上问题的根本原因在于,并没有充分理解QThread只是一个接口的本质。那么应该如何正确的让程序在新线程中得以执行呢?答案是将需要在新线程中运行的对象moveTo到QThread中,而非继承QThread并把自身moveTo到新线程空间中。
    由此我们提出应用QThread的以下几个重要原则。
 
QThread应用原则:
1.QThread只是系统执行线程的接口而已,并不是用于编写代码的;
2.在当前线程(如:线程A)上下文中创建的对象属于当前线程,其他线程(如:线程B、C、D...)不可以操作属于当前线程(如:线程A)的对象;
3.当前线程(如:线程A)中基于OBject类的对象可以被移动到其他线程(如:线程B、C、D...);
4.当前线程(如:线程A)中基于OBject类的对象在移动到其他线程(如:线程B、C、D...)去执行的时候,要求目标线程(如:线程B、C、D...)已经开始运行;
 
    由2可以推出,如果当前线程(如:线程A)中,基于OBject类的对象被移动到其他线程(如:线程B、C、D...)之后,该对象只能由目标线程(如:线程B、C、D...)负责释放。
    另外,在将信号与被moveTo到新线程中的对象所拥有的槽相连接时,需要注意连接的方式。
 
注意:
    信号与槽的连接方式有:Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection和Qt::BlockingQueuedConnection。
Qt::AutoConnection:是根据对象所在线程不同而选择Qt::DirectConnection或Qt::QueuedConnection;
Qt::DirectConnection:用于同一个线程当中,相当于直接函数调用,槽函数执行完后才返回;
Qt::QueuedConnection:用于不同的线程当中,会建立一个队列,槽函数立即返回,而不用等待队列中的信号执行完毕;
Qt::BlockingQueuedConnection:也是用于不同线程的,但是又相当于函数调用,因为要等到槽函数执行完毕才能够返回。
 
示例:
    在此,提供一个应用QThread的示例,该示例中打开一个串口用于接收数据,但为了同时兼顾UI对用户的响应,需要为串口接收程序单独建立一个线程。由于串口对象被moveTo到了新线程中,因此无法在UI线程中关闭串口,因此要用到QThread的finished()信号。
    这只是一个示例,代码的编写更注重演示效果,而非其他。
    该示例的工程组织如下:
    
uiwindow.ui文件中窗体为初始化状态。
Serial.pro 文件内容如下:
--------------------------------------------------------------------------
#-------------------------------------------------
#
# Project created by QtCreator 2014-07-18T15:41:22
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

greaterThan(QT_MAJOR_VERSION, 4) {
    QT       += widgets serialport
} else {
    include($$QTSERIALPORT_PROJECT_ROOT/src/serialport/qt4support/serialport.prf)
}

TARGET = Serial
TEMPLATE = app


SOURCES += main.cpp\
        uiwindow.cpp \
    serial.cpp

HEADERS  += uiwindow.h \
    serial.h

FORMS    += uiwindow.ui
--------------------------------------------------------------------------

serial.h 文件内容如下:
--------------------------------------------------------------------------
#ifndef SERIAL_H
#define SERIAL_H

#include <QObject>
#include <QtSerialPort/QSerialPort>

class Serial : public QObject
{
    Q_OBJECT
public:
    explicit Serial(QObject *parent = 0);
    ~Serial(void);
    QSerialPort *port;
    
signals:
    
public slots:
    void readData(void);
    void threadStarted(void);
    void threadFinished(void);
    
};

#endif // SERIAL_H
--------------------------------------------------------------------------

serial.cpp 文件内容如下:
--------------------------------------------------------------------------
#include "serial.h"
#include <QMessageBox>
#include <QDebug>
#include <QThread>

Serial::Serial(QObject *parent) :
    QObject(parent)
{
    port = new QSerialPort();
    port->setPortName("COM1");
    if(!port->open(QSerialPort::ReadWrite))
    {
        QMessageBox WrrMsg;
        WrrMsg.setInformativeText("无法打开该串口");
        WrrMsg.show();
        WrrMsg.exec();
    }
    port->setBaudRate(QSerialPort::Baud19200,QSerialPort::AllDirections);   // 19200,N,8,1
    port->setDataBits(QSerialPort::Data8);
    port->setStopBits(QSerialPort::OneStop);
    port->setParity(QSerialPort::NoParity);
    port->setFlowControl(QSerialPort::NoFlowControl);
    connect(port, SIGNAL(readyRead()), this, SLOT(readData()), Qt::DirectConnection);   // 注意,真正执行时 port 与 Serial 在同一个线程中,因此使用 Qt::DirectConnection。
}

Serial::~Serial(void)
{
}

void Serial::readData(void)
{
    qDebug()<< "Reading Data...ID is:" << QThread::currentThreadId();
    port->clear(QSerialPort::AllDirections);
}

void Serial::threadStarted(void)
{
    qDebug()<< "Thread has started...ID is:" << QThread::currentThreadId();
}

void Serial::threadFinished(void)
{
    qDebug()<< "Closing COM port...ID is:" << QThread::currentThreadId();
    if(port->isOpen())
    {
        port->close();      // 关闭串口。
    }
}
--------------------------------------------------------------------------

uiwindow.h 文件内容如下:
--------------------------------------------------------------------------
#ifndef UIWINDOW_H
#define UIWINDOW_H

#include <QMainWindow>
#include <QThread>
#include "serial.h"

namespace Ui {
class UIWindow;
}

class UIWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    explicit UIWindow(QWidget *parent = 0);
    ~UIWindow();

private:
    Ui::UIWindow *ui;
    QThread serialThread;
    Serial *serial;
};

#endif // UIWINDOW_H
--------------------------------------------------------------------------

uiwindow.cpp 文件内容如下:
--------------------------------------------------------------------------
#include "uiwindow.h"
#include "ui_uiwindow.h"
#include <QDebug>

UIWindow::UIWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::UIWindow)
{
    ui->setupUi(this);

    qDebug()<< "UI thread ID is:" << QThread::currentThreadId();

    serial = new Serial();
    connect(&serialThread, SIGNAL(started()), serial, SLOT(threadStarted()), Qt::QueuedConnection);     // 注意,serialThread 与 serial 并不在同一个线程中,因此使用 Qt::QueuedConnection。
    connect(&serialThread, SIGNAL(finished()), serial, SLOT(threadFinished()), Qt::DirectConnection);   // serialThread 的 finished() 信号是在新线程中执行的,因此此处要使用 Qt::DirectConnection。

    serialThread.start(QThread::HighestPriority);   // 开启线程,串口接收线程的优先级较高。
    serial->moveToThread(&serialThread);            // 将串口接受对象移动到新线程中。
    serial->port->moveToThread(&serialThread);      // 用于接收的 port 一并移入新线程中。
}

UIWindow::~UIWindow()
{
    if(serialThread.isRunning())
    {
serialThread.exit();                // 结束该线程。
        serialThread.wait();
        /*while(!serialThread.isFinished())
        {
            ;
        }*/
    }
    delete ui;
}
--------------------------------------------------------------------------

http://blog.csdn.net/desert187/article/details/37932999

QT之深入理解QThread的更多相关文章

  1. Qt Model/View理解(二)---构造model(细心研读,发现超简单,Model就是做三件事:返回行数量、列数量、data如何显示。然后把model与view联系起来即可,两个例子都是如此)good

    数据是一个集合,显示也是一个集合.例如一篇<西游记>的文章,所有的文字就是数据集合,展示方式就是显示的集合,可以以书本的形式,也可以以电纸书的形式,更可以用视频的方式展现. 下面是将一个二 ...

  2. 【QT】子类化QThread实现多线程

    <QThread源码浅析> 子类化QThread来实现多线程, QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里.正确启动线程的方法是调用QThrea ...

  3. Qt——线程类QThread

    本文主要介绍Qt中线程类QThread的用法,参考(翻译+修改)了一篇文章:PyQt: Threading Basics Tutorial,虽然使用的是PyQt,但与C++中Qt的用法大同小异,不必太 ...

  4. 【Qt开发】QThread介绍

    回顾Qt之线程(QThread),里面讲解了如何使用线程,但还有很多人留言没有看明白,那么今天我们来一起瞅瞅关于QThread管理线程的那些事儿... 一.线程管理 1.线程启动 void start ...

  5. 【QT】 QThread部分源码浅析

    本文章挑出QThread源码中部分重点代码来说明QThread启动到结束的过程是怎么调度的.其次因为到了Qt4.4版本,Qt的多线程就有所变化,所以本章会以Qt4.0.1和Qt5.6.2版本的源码来进 ...

  6. 【QT】QThread源码浅析

    本章会挑出QThread源码中部分重点代码来说明QThread启动到结束的过程是怎么调度的.其次因为到了Qt4.4版本,Qt的多线程就有所变化,所以本章会以Qt4.0.1和Qt5.6.2版本的源码来进 ...

  7. Qt经典—线程、事件与Qobject(耳目一新)

    介绍 You’re doing it wrong. — Bradley T. Hughes 线程是qt channel里最流行的讨论话题之一.许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到 ...

  8. 【转】Qt事件循环与线程 二

    转自:http://blog.csdn.net/changsheng230/article/details/6153449 续上文:http://blog.csdn.net/changsheng230 ...

  9. Qt经典—线程、事件与Qobject

    介绍 You’re doing it wrong. — Bradley T. Hughes 线程是qt channel里最流行的讨论话题之一.许多人加入了讨论并询问如何解决他们在运行跨线程编程时所遇到 ...

随机推荐

  1. r语言之条件、循环语句

    if条件语句:if (conditon) {expr1} else {expr2} > x<-1> if(x==1)+ {x<-"x=1"}else+ {x ...

  2. [LeetCode]题解(python):065-Valid Number

    题目来源: https://leetcode.com/problems/valid-number/ 题意分析: 输入一个字符串,判断这个字符串表示的是不是一个有效的数字.比如: "0&quo ...

  3. javascript 检测密码强度

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. MFC的最大化,最小化,关闭

    最大化.最小化和关闭按钮是窗口中最主要的元素.首先要说明,说他们是按钮其实是不准确的,按钮是一种窗口,而这三个组件根本就不是窗口,而是一个窗口常见的组成部分.出于习惯的原因,这里还是称呼他们为按钮. ...

  5. BZOJ 网站镜像

    网站镜像大小:15Mb 镜像保存时间:2012年2月[有绝大多数题目] 镜像下载地址:http://download.csdn.net/detail/wnjxyk/7913125

  6. 1369 - Answering Queries(规律)

    1369 - Answering Queries   PDF (English) Statistics Forum Time Limit: 3 second(s) Memory Limit: 32 M ...

  7. DDFT

    得知DIP文章4日 傅立叶变换数学原理将可能完全被引入,下仅仅实现代码.观察下结果,公式在上一篇博客中已经描写叙述 上代码: // // main.c // Fourer2D // // Create ...

  8. 读懂Swift 2.0中字符串设计思路的改变

    Swift提供了一种高性能的,兼容Unicode编码的String实现作为标准库的一部分.在 Swift2中,String类型不再遵守CollectionType协议.在以前,String类型是字符的 ...

  9. Jquery获对HTML控件的控制

    Jquery获对HTML控件的控制 1.获取控件的值 1.1.radio 1.1.1 获取一组radio被选中项的值  var item = $('input[name=items][checked] ...

  10. C#_会员管理系统:开发五(用户注册)

    创建一个新的用户注册窗体(VIPRegistration.cs): 用户注册窗体(VIPRegistration.cs)详细代码如下: using System; using System.Colle ...