QT之深入理解QThread

摘要:
QThread的出口是finished()信号。上述问题的根本原因是我们没有完全理解QThread只是一个接口。答案是将需要在新线程中运行的对象移动到QThread,而不是继承QThread并将其自身移动到新线程空间。因此,我们提出了应用QThread的以下重要原则。

QT之深入理解QThread

 
    理解QThread之前需要了解下QThread类,QThread拥有的资源如下(摘录于QT 5.1 帮助文档):
 
    在以上资源中,本文重点关注槽:start();信号:started()、finished();受保护的方法:run()、exec();
 
理解QThread
    QThread与通常所熟知的线程(thread)有很大出入,在面向过程的语言中,我们建立一个线程的同时会传入一个函数名,这个函数名代表该线程要执行的具体代码(如图 1 所示)。
QT之深入理解QThread第1张
图 1. 我们通常所理解的线程
    但是QThread里并没有线程的具体代码,QThread只是一个接口而已,目的是为操作系统提供一个用于线程调度的“句柄”。这个“句柄”即是QThread的入口(如图 2 所示)。
QT之深入理解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()信号。
    这只是一个示例,代码的编写更注重演示效果,而非其他。
    该示例的工程组织如下:
    QT之深入理解QThread第3张
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》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇开源一个功能完整的SpringBoot项目框架苹果服务器证书生成步骤下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

性能测试三十二:监控之Java线程监控

线程的五种状态* 新建:new* 运行:runnable* 等待:waitting(无限期等待),timed waitting(限期等待)* 阻塞:blocked* 结束:terminated 线程的两种监控方法一,jvisualvm,图形界面的方式监控之前先对jvm加监控参数,在tomcat的bin目录下,catalina.sh文件中,第二行添加:JA...

C++面试

https://blog.csdn.net/weixin_44363885/article/details/99567746 这一行是个 贼鸡巴重要的链接!!!   很好的总结 我直接复制到下面了: 社招:社招的同学,无论是1-3年经验,还是中途转行,都可参考。写简历必须有针对性,以后台开发为例,请去拉勾网 / 猎聘 / 智联招聘等网站,多看看后台开发的J...

Linux下的sleep()和sched_yield()(转)

阿里四面被问到了这个问题,一脸懵逼,下来也没找到什么阐述这个的文章,就自己查man来对比总结一下吧: sched_yield()的man手册描述如下: DESCRIPTION       sched_yield()  causes  the  calling  thread to relinquish the CPU.  The  thread is mo...

golang 之GPM模型

1、Golang调度器的由来 2、Goroutine调度器的GMP模型及设计思想 3、Goroutine调度场景过程全图文解析 早期的单进程操作系统,面临2个问题: 1.单一的执行流程,计算机只能一个任务一个任务处理。 2.进程阻塞所带来的CPU时间浪费。 多进程/线程时代有了调度器需求: 进程拥有太多的资源,进程的创建、切换、销毁,都会占用很长的时间,...

使用 suspend 和 resume 暂停和恢复线程

suspend 和 resume 的使用 在 Thread 类中有这样两个方法:suspend 和 resume,这两个方法是成对出现的。 suspend() 方法的作用是将一个线程挂起(暂停), resume() 方法的作用则是将一个挂起的线程重新开始并继续向下运行。 通过一个例子来看一下这两个方法的使用: public class SuspendT...

【转】编写高质量代码改善C#程序的157个建议——建议85:Task中的异常处理

建议85:Task中的异常处理在任何时候,异常处理都是非常重要的一个环节。多线程与并行编程中尤其是这样。如果不处理这些后台任务中的异常,应用程序将会莫名其妙的退出。处理那些不是主线程(如果是窗体程序,那就是UI主线程)产生的异常,最终的办法都是将其包装到主线程上。在任务并行库中,如果对任务运行Wait、WaitAny、WaitAll等方法,或者求Resul...