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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458615.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springMVC数据封装成POJO

springMVC把前台的数据封装为POJO与struts2的封装形式不同。struts2需要在控制器声明需封装的POJO&#xff0c;而springMVC不需要任何准备工作&#xff0c;只需在相应的方法的参数中加上需封装的POJO&#xff0c;当用户提交表单时&#xff0c;springMVC会根据表单中dom元素的na…

10.11 安装pod

原文地址&#xff1a;http://www.jianshu.com/p/5fc15906c53a 感谢。 更新升级10.11 cocoapods安装出问题最简单的解决方法 这是因为10.11把cocoapods直接干掉了 sudo gem install -n /usr/local/bin cocoapods 再加一句&#xff0c;完美解决 sudo xcode-select --switch /App…

JavaScript中的原型继承原理

在JavaScript当中&#xff0c;对象A如果要继承对象B的属性和方法&#xff0c;那么只要将对象B放到对象A的原型链上即可。而某个对象的原型链&#xff0c;就是由该对象开始&#xff0c;通过__proto__属性连接起来的一串对象。__proto__属性是JavaScript对象中的内部属性&#xf…

建模元件有哪些在MapleSim中

信号库&#xff1a;包含通用信号模块、布尔、控制器、离散信号模块、信号源、线性信号模块、非线性信号模块、时间离散信号模块、查询表、信号转换器、数学运算、关系元件、特殊信号模块&#xff0c;应用案例。 电子库&#xff1a;包含电阻、运算放大器、二极管、步进电机、模拟…

【C++】VS2010将写好的程序打包成安装文件发布

参考链接&#xff1a;http://blog.csdn.net/yongh701/article/details/51326142 我们可以将自己写好的VS2010程序打包成安装文件&#xff0c;给用户安装&#xff0c;具体步骤如下&#xff1a; 1、如下图&#xff0c;同样是新建一个项目&#xff0c;但是这次是新建一个其它项目…

01_jeecms建站

一、环境安装 JDK5TOMCAT5.5MYSQL5及以上http://www.jeecms.com/tutorial/index.jhtml参考环境安装篇二、解压文件安装包jeecms-v5zip,如图图1ROOT文件夹复制放到tomcat下的webapps文件夹&#xff08;注&#xff1a;请先删除webapps下原有的默认ROOT文件夹&#xff09;如不想部…

WiFi基本知识

转自&#xff1a;http://blog.csdn.net/myarrow/article/details/7930131 1. IE802.11简介 标准号IEEE 802.11bIEEE 802.11aIEEE 802.11gIEEE 802.11n标准发布时间1999年9月1999年9月2003年6月2009年9月工作频率范围2.4&#xff0d;2.4835GHz 5.150&#xff0d;5.350GHz5.475&a…

libev 宏展开

想看源码&#xff0c;宏太多&#xff0c;看着累&#xff0c;宏展开&#xff0c;再看&#xff0c;功力时间不够&#xff0c;先放下 放上宏展开后的代码。 libev4.20 展开方示为 ./configure 修改makefile文件&#xff0c;字符串 替换CC为 CPP 注意要把基础的CC定义保留 make mv …

FreeRTOS高级篇7---FreeRTOS内存管理分析

原文&#xff1a;http://blog.csdn.net/zhzht19861011/article/details/51606068 内存管理对应用程序和操作系统来说都非常重要。现在很多的程序漏洞和运行崩溃都和内存分配使用错误有关。 FreeRTOS操作系统将内核与内存管理分开实现&#xff0c;操作系统内核仅规定了必要的内…

FreeRTOS学习笔记——互斥型信号量

来自&#xff1a;http://blog.csdn.net/xukai871105/article/details/43456985 0.前言 在嵌入式操作系统中互斥型信号量是任务间资源保护的重要手段。下面结合一个具体例子说明FreeRTOS中的互斥型信号量如何使用。 【相关博文】 【FreeRTOS STM32移植笔记】 【FreeRTOS学习笔记…

FreeRTOS系列第19篇---FreeRTOS信号量

来自&#xff1a;http://blog.csdn.net/zhzht19861011/article/details/50835613 本文介绍信号量的基础知识&#xff0c;详细源码分析见《FreeRTOS高级篇6---FreeRTOS信号量分析》 1.信号量简介 FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量&#xff08;以后简称…

蓝牙HCI剖析(一)

来自&#xff1a;http://blog.csdn.net/xiaoxiaopengbo/article/details/51334257 一.HCI介绍 HCI提供了访问bluetooth control的统一接口&#xff0c;通俗来讲&#xff0c;就是定义了特定的格式来控制蓝牙芯片来做相应的动作&#xff08;比如inquiry,connect,disconnect&#…

ASP.NET状缓存Cache的应用-提高数据库读取速度

ASP.NET状缓存Cache的应用-提高数据库读取速度 原文:ASP.NET状缓存Cache的应用-提高数据库读取速度一、 Cache概述 既然缓存中的数据其实是来自数据库的&#xff0c;那么缓存中的数据如何和数据库进行同步呢&#xff1f;一般来说&#xff0c;缓存中应该存放改动不大或者对…

入门级----测试的执行、环境的搭建、每日构建、测试记录和跟踪、回归测试、测试总结和报告...

测试用例的准备&#xff0c;都是为了执行测试准备的。 测试环境的搭建 &#xff08;1&#xff09;测试数据&#xff1a;有些测试需要使用大批量的数据&#xff0c;例如容量测试、压力测试等。根据产品的具体测试要求&#xff0c;可能需要在数据库表插入大量的数据&#xff0c;准…

限制MySQL Binlog的传输速率

最近一台核心库备库完成恢复后打开slave&#xff0c;导致主库传送binlog&#xff0c;瞬间占满网络&#xff0c;触发故障。 为了做一些限制&#xff0c; 给mysql在发送binlog的函数(mysql_binlog_send)里每隔一段时间sleep一次&#xff0c; 增加了两个参数&#xff1a; master_s…

掌握 Ajax,第 2 部分: 使用 JavaScript 和 Ajax 发出异步请求

转http://www.ibm.com/developerworks/cn/xml/wa-ajaxintro2/ 掌握 Ajax&#xff0c;第 2 部分: 使用 JavaScript 和 Ajax 发出异步请求 在 Web 请求中使用 XMLHttpRequest 多数 Web 应用程序都使用请求/响应模型从服务器上获得完整的 HTML 页面。常常是点击一个按钮&#xff0…

Provisioning Services 7.8 入门系列教程之十一 通过版本控制自动更新虚拟磁盘

续Provisioning Services 7.8 入门系列教程之十 通过类自动更新虚拟磁盘从前两的两种更新方式可以看出&#xff0c;它们有一个共同的特点&#xff0c;即需要产生&#xff08;复制&#xff09;完成的虚拟磁盘副本&#xff0c;然后进行相关的升级操作。这两种方法在实际生产中&am…

登录失败时记住访问的地址

登录失败时记住访问的地址 使用spring MVC 访问时,在拦截器中记录访问的地址: Java代码 String path request.getRequestURI();//"/demo_channel_terminal/news/list" System.out.println("您无权访问:" path); //用于登录成功…

Tomcat - Maven plugin: 运行找不到webapp

2019独角兽企业重金招聘Python工程师标准>>> The tomcat7-maven-plugin allows running the current project as a Web application and additional <webapps> can be specified that will be simultaneously loaded into tomcat. My project is not a Web ap…

基于MQTT协议进行应用开发

来自&#xff1a;http://www.cnblogs.com/secondtononewe/p/6073089.html 官方协议有句如下的话来形容MQTT的设计思想&#xff1a; “It is designed for connections with remote locations where a "small code footprint" is required or the network bandwidth i…