QT之深入理解QThread

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
时间: 2024-10-20 20:11:34

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

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

数据是一个集合,显示也是一个集合.例如一篇<西游记>的文章,所有的文字就是数据集合,展示方式就是显示的集合,可以以书本的形式,也可以以电纸书的形式,更可以用视频的方式展现. 下面是将一个二维数组中的数据显示到TableView控件中. 1.声明一个model类,继承于QAbstractTableModel #define ROW 3#define COL 2 class TableModel : public QAbstractTableModel{ Q_OBJECTpublic: Table

Qt经典—线程、事件与Qobject

(转自:http://www.cnblogs.com/newstart/archive/2013/07/20/3202118.html) 先决条件 考虑到本文并不是一个线程编程的泛泛介绍,我们希望你有如下相关知识: C++基础: Qt 基础:QOjbects , 信号/槽,事件处理: 了解什么是线程.线程与进程间的关系和操作系统: 了解主流操作系统如何启动.停止.等待并结束一个线程: 了解如何使用mutexes, semaphores 和以及wait conditions 来创建一个线程安全/可

QT核心编程之Qt线程 (c)

QT核心编程之Qt线程是本节要介绍的内容,QT核心编程我们要分几个部分来介绍,想参考更多内容,请看末尾的编辑推荐进行详细阅读,先来看本篇内容. Qt对线程提供了支持,它引入了一些基本与平台无关的线程类.线程安全传递事件的方式和全局Qt库互斥量允许你从不同的线程调用Qt的方法.Qt中与线程应用相关的类如表6所示. 表6 Qt中与线程相关的类 使用线程需要Qt提供相应的线程库的支持,因此,在编译安装Qt时,需要加上线程支持选项. 当在Windows操作系统上编译Qt时,线程支持是在一些编译器上的一个

VS中Qt的探索02

边看C++ GUI QT4教程,边在VS2010中进行编程学习探索. 在使用Qt设计师时,其中每一个对象的ObjectName属性是非常重要的,在程序功能的实现过程中,需要不断的使用该变量名. 当所有的对象属性设置完之后,在VS2010中右击*.ui文件,选择编译(ctrl+F7),便会自动生成另一个.h文件,里面的某一个类包含了对象的所有属性情况,如下图所示: 并且在VS自动生成的*.h文件中,会有一个该类的变量名用来访问你在Qt设计师中更改的每个对象的属性值,从而我们可以在实现某项功能时进行

qt多线程

[TOC] 1. QT 线程的创建 Qthread MovetoThread MovetoThread需要重新connect A:Qthread B:MovetoThread C:-->?MovetoThread需要重新connect 2. QT 线程同步 互斥量 QMutex 读写锁 QReadWriteLock 信号量 QSempahore 等待条件 QWaitCondition A:互斥量 QMutex B:读写锁 QReadWriteLock C:信号量 QSempahore D:等待条

从pthread 到QThread

该文出自:http://www.civilnet.cn/bbs/topicno/78430 使用线程技术进行应用编程的意义在gemfield的文章<从进程到线程>中已经介绍过了,我们就直奔主题:Linux上的线程开发以及Qt对线程的封装(Linux平台部分).Linux上的线程API使用的是pthread库,我们来粗略认识下pthread. 要在程序中使用pthread线程,究竟会用到哪几部分的功能呢? 1.创建线程是必须的吧,pthread_create(): 2.设置线程的属性也是需要的吧

Qt [email&#160;protected]学习日志

day 1: Qt中类: 理解一个类最好还是从其类代码实现上看. 由此图可看出需要好好研究那几个重要的类:Qt/QEvent/QObject/QWidget/. QApplication: (比较复杂,且很常用) 它使用用户的桌面设置,例如palette().font()和doubleClickInterval()来初始化应用程序.如果用户改变全局桌面,例如通过一些控制面板,它会对这些属性保持跟踪. 它执行事件处理,也就是说它从低下的窗口系统接收事件并且把它们分派给相关的窗口部件.通过使用sen

Qt on Android: Qt Quick 事件处理之信号与槽

前面两篇文章< Qt on Android:QML 语言基础>和<Qt on Android: Qt Quick 简单教程>中我们介绍了 QML 语言的基本语法和 Qt Quick 的常见元素,亲们,通过这两篇文章,您应该已经可以完成简单的 Qt Quick 应用了.接下来呢,哈,我们要介绍 Qt Quick 中一个灰常灰常重要的主题:事件处理.这将是比较长长长长的一篇,哦,不还有后续好几篇--废话少说,还是谈正事儿吧兄弟姐妹们. 本文参加 CSDN 博文大赛,请点这里投我一票,谢

Qt Quick里的粒子系统

就差您这一票了亲:博客之星评选,点击投我一票,谢谢.投过了也可以点哦,每天都可以投投一票. Qt Quick提供了一个粒子系统,提供了四种主要的 QML 类型: ParticleSystem ,粒子系统,它维护一个粒子系统相关的 Emitters . Painters . Affectors ,Emitters . Painters . Affectors 要想一起玩儿,就得指定同一个 ParticleSystem. ParticleSystem Painters , 它负责渲染一个粒子.Par