Qt之QThread(深入理解)

简述

前面,我们介绍了QThread常用的两种方式:

  • worker-object
  • 子类化QThread

下面,我们首先来看看子类化QThread在日常中的应用。

  • 简述
  • 子类化QThread
  • 在主线程中更新UI
  • 正常结束线程
  • 更多参考

一般情况下,QThread进行耗时操作的同时会与UI进行交互,比如:显示进度、旋转等待。。。进行友好型的交互,让用户知道当前的操作。

子类化QThread

我们以更新进度条为例,来模拟一个耗时操作,并分享我们有可能在此过程中遇到的问题及解决办法。

首先,我们定义一个WorkerThread 类,让其继承自QThread,并重写run()函数,每隔50毫秒更新下当前的值,然后发射resultReady()信号(用于更新进度条)。

#include <QThread>

class WorkerThread : public QThread
{
    Q_OBJECT

public:
    explicit WorkerThread(QObject *parent = 0)
        : QThread(parent)
    {
        qDebug() << "Worker Thread : " << QThread::currentThreadId();
    }

protected:
    virtual void run() Q_DECL_OVERRIDE {
        qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
        int nValue = 0;
        while (nValue < 100)
        {
            // 休眠50毫秒
            msleep(50);
            ++nValue;

            // 准备更新
            emit resultReady(nValue);
        }
    }
signals:
    void resultReady(int value);
};

构建一个主界面,用于显示进度条。

class MainWindow : public CustomWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0)
        : CustomWindow(parent)
    {
        qDebug() << "Main Thread : " << QThread::currentThreadId();

        // 创建开始按钮、进度条
        QPushButton *pStartButton = new QPushButton(this);
        m_pProgressBar = new QProgressBar(this);

        //设置文本、进度条取值范围
        pStartButton->setText(QString::fromLocal8Bit("开始"));
        m_pProgressBar->setFixedHeight(25);
        m_pProgressBar->setRange(0, 100);
        m_pProgressBar->setValue(0);

        QVBoxLayout *pLayout = new QVBoxLayout();
        pLayout->addWidget(pStartButton, 0, Qt::AlignHCenter);
        pLayout->addWidget(m_pProgressBar);
        pLayout->setSpacing(50);
        pLayout->setContentsMargins(10, 10, 10, 10);
        setLayout(pLayout);

        // 连接信号槽
        connect(pStartButton, SIGNAL(clicked(bool)), this, SLOT(startThread()));
    }

    ~MainWindow(){}

private slots:
    // 更新进度
    void handleResults(int value)
    {
        qDebug() << "Handle Thread : " << QThread::currentThreadId();
        m_pProgressBar->setValue(value);
    }

    // 开启线程
    void startThread()
    {
        WorkerThread *workerThread = new WorkerThread(this);
        connect(workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
        // 线程结束后,自动销毁
        connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
        workerThread->start();
    }

private:
    QProgressBar *m_pProgressBar;
    WorkerThread m_workerThread;
};

显然,主线程、Worker构造所在的线程、槽函数处于同一线程,而run()函数处于其它线程。

Main Thread : 0x34fc

Worker Thread : 0x34fc

Worker Run Thread : 0x4038

Handle Thread : 0x34fc

在主线程中更新UI

当连接方式改为“Qt::DirectConnection”时:

connect(workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)), Qt::DirectConnection);

会出现以下错误:

ASSERT failure in QCoreApplication::sendEvent: “Cannot send events to objects owned by a different thread. Current thread e346e8. Receiver customWidget’ (of type ‘MainWindow’) was created in thread 4186a0”, file kernel\qcoreapplication.cpp, line 553

这时,线程的运行情况如下所示:

Main Thread : 0x2c6c

Worker Thread : 0x2c6c

Worker Run Thread : 0x4704

Handle Thread : 0x4704

也就是说,run()函数与槽函数所在的是同一个线程。

根据Qt之Threads和QObjects中“跨线程的信号和槽”部分,提到的“Direct Connection:当信号发射后,槽函数立即被调用。槽函数在信号发射者所在的线程中执行,而未必需要在接收者的线程中。”

所以,不难理解,因为我们在子线程中更新了UI。

正常结束线程

上述代码有一些问题,都是我们经常遇到的。

  • 多次connect信号与槽:当多次点击“开始”按钮的时候,就会启动多个线程,同时更新进度条。
  • 暴力终止线程:而且当用户关闭窗体的时候,线程有可能还在运行,这时极有可能发生未知错误。

正如前面提到过terminate(),比较危险,不鼓励使用。线程可以在代码执行的任何点被终止。线程可能在更新数据时被终止,从而没有机会来清理自己,解锁等等。。。总之,只有在绝对必要时使用此函数。

举一个简单的例子:当你的哥们正在酣睡时,你突然泼了一盆凉水,想象一下后果?O(∩_∩)O哈哈~

为了避免以上问题,我们先修改如下:

class MainWindow : public CustomWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0)
        : CustomWindow(parent)
    {
        // ...
        connect(&m_workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
    }

    ~MainWindow(){}

private slots:
    // ...
    void startThread()
    {
        if (!m_workerThread.isRunning())
            m_workerThread.start();
    }

private:
    WorkerThread m_workerThread;
};

这时,不会出现启动多个线程的问题。但是如果线程正在运行,我们就关闭主界面,则会出现另外一个很常见的问题:

QThread: Destroyed while thread is still running

这是因为次线程还在运行,就结束了UI主线程。这个问题在耗时操作中经常遇到,我们可以采用以下方式来避免:

  • QMutex互斥锁 + bool成员变量。
#include <QThread>
#include <QMutexLocker>

class WorkerThread : public QThread
{
    Q_OBJECT

public:
    explicit WorkerThread(QObject *parent = 0)
        : QThread(parent),
          m_bStopped(false)
    {
        qDebug() << "Worker Thread : " << QThread::currentThreadId();
    }

    ~WorkerThread()
    {
        stop();
        quit();
        wait();
    }

    void stop()
    {
        QMutexLocker locker(&m_mutex);
        m_bStopped = true;
    }

protected:
    virtual void run() Q_DECL_OVERRIDE {
        qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
        int nValue = 0;
        while (nValue < 100)
        {
            // 休眠50毫秒
            msleep(50);
            ++nValue;

            // 准备更新
            emit resultReady(nValue);

            // 检测是否停止
            {
                QMutexLocker locker(&m_mutex);
                if (m_bStopped)
                    break;
            }
            // locker超出范围并释放互斥锁
        }
    }
signals:
    void resultReady(int value);

private:
    bool m_bStopped;
    QMutex m_mutex;
};
  • requestInterruption() + isInterruptionRequested()

这两个接口是Qt5以后引入的,使用很方便:

class WorkerThread : public QThread
{
    Q_OBJECT

public:
    explicit WorkerThread(QObject *parent = 0)
        : QThread(parent)
    {
    }

    ~WorkerThread() {
        // 请求终止
        requestInterruption();
        quit();
        wait();
    }

protected:
    virtual void run() Q_DECL_OVERRIDE {
        // 是否请求终止
        while (!isInterruptionRequested())
        {
            // 耗时操作
        }
    }
};

在耗时操作中使用isInterruptionRequested()来判断是否请求终止线程,如果没有,则一直运行;当希望终止线程的时候,调用requestInterruption()即可。

更多参考

时间: 2024-10-20 09:42:09

Qt之QThread(深入理解)的相关文章

解析Qt中QThread使用方法

本文讲述的是在Qt中QThread使用方法,QThread似乎是很难的一个东西,特别是信号和槽,有非常多的人(尽管使用者本人往往不知道)在用不恰当(甚至错误)的方式在使用QThread,随便用google一搜,就能搜出大量结果出来.无怪乎Qt的开发人员 Bradley T. Hughes 声嘶力竭地喊you are-doing-it-wrong 和众多用户一样,初次看到这个时,感到 Bradley T. Hughes有 些莫名奇妙,小题大作.尽管不舒服,当时还是整理过一篇博客QThread 的使

Qt多线程-QThread

版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt多线程-QThread     本文地址:http://techieliang.com/2017/12/592/ 文章目录 1. 介绍  1.1. 线程优先级  1.2. 线程休眠 2. 基本使用  2.1. 建立QThread子类法  2.2. moveToThread方法 3. 线程同步  3.1. QMutex互斥量  3.2. QMutexLocker  3.3. QReadWr

Qt之QThread随记

这是一篇随记,排版什么的就没有那么好了:) 首先要知道,一个线程在资源分配完之后是以某段代码为起点开始执行的,例如STL内的std::thread,POSIX下的pthread等,都是以函数加其参数之后在新线程内调用运行的,但是,Qt的却进行了一个封装,要使用Qt的QThread,核心思想就是将对象在线程内创建或移入线程中,之后通过那些在线程内的对象的信号和槽方式与其他线程进行交互,这里就衍生出来了两种写法(虽然核心思想都是一样的): 第一种: ... class myThread: publi

QT下QThread学习(二)

学习QThread主要是为了仿照VC下的FTP服务器写个QT版本.不多说,上图. FTP服务器的软件结构在上面的分析中就已经解释了,今天要解决的就是让每一个客户端的处理过程都可以按一个线程来单独跑.先给出上面3个类的cpp文件,再给出现象. 1.QListenSocket类 1 #include "qlistensocket.h" 2 #include <QTcpSocket> 3 #include <QDebug> 4 5 QListenSocket::QLi

Qt线程—QThread的使用--run和movetoThread的用法

Qt使用线程主要有两种方法: 方法一:继承QThread,重写run()的方法 QThread是一个非常便利的跨平台的对平台原生线程的抽象.启动一个线程是很简单的.让我们看一个简短的代码:生成一个在线程内输出"hello"并退出的线程. // hellothread/hellothread.h class HelloThread : public QThread { Q_OBJECT private: void run(); }; 我们从QThread派生出一个类,并重新实现run方法

Qt:走马灯代码理解

走马灯是编程学习中常用到的示例代码.在这里把从中学到的知识进行一个汇总.如有不对之处,请各位指正. 一:思路 走马灯首先要考虑是在特定时间点将显示内容向一个方向移动,当显示最后有空位时,循环填写显示内容. 要有一个定时器:处理定时器的事件:显示的文本内容:当显示内容在控件中移动时,需要重绘控件. 当控件隐藏后,停止走马灯. 处理控件隐藏事件. 当控件显示时,开始走马灯. 处理控件显示事件. 二:头文件 #ifndef MAINWIDGET_H#define MAINWIDGET_H #inclu

Qt之Q_PROPERTY宏理解

在初学Qt的过程中,时不时地要通过F2快捷键来查看QT类的定义,发现类定义中有许多Q_PROPERTY的东西,比如最常用的QWidget的类定义: Qt中的Q_PROPERTY宏在Qt中是很常用的,那么它有什么作用呢? Qt提供了一个绝妙的属性系统,Q_PROPERTY()是一个宏,用来在一个类中声明一个属性property,由于该宏是qt特有的,需要用moc进行编译,故必须继承于QObject类. Q_PROPERTY(type name READ getFunction [WRITE set

Qt中setGeometry的理解

如果在控件中加上了layout布局,就会发现发现没有办法使用setGeometry函数了,这是因为布局已经被layout管理,没你啥事了. 但是父控件被layout管理,父控件的子控件却没有啊 ,所以在创建子控件的时候,需要指定子控件的父控件是谁.这样子控件就可以使用 setGeometry函数,可以自由的调整位置,但是只能在父控件的范围内调整位置,同时必须注意setGeometry之后一定要调用show函数, 否则可能看不到控件存在.

Qt QThread必须要了解的几个函数

概述 如果想对Qt中的QThread有个更加深刻的了解,必须要知道这几个重要的函数,现在就一一介绍下. 函数介绍 属性 返回值 函数体 功能 static QThread * QThread::currentThread() 返回当前线程的指针,静态函数. static Qt::HANDLE QThread::currentThreadId() 返回当前线程的句柄,静态函数 static bool QThread::isFinished() const 如果线程执行结束,返回true,否则返回f