一文詳解Qt中線程的實際應(yīng)用
為了讓程序盡快響應(yīng)用戶操作,在開發(fā)應(yīng)用程序時經(jīng)常會使用到線程。對于耗時操作如果不使用線程,UI界面將會長時間處于停滯狀態(tài),這種情況是用戶非常不愿意看到的,我們可以用線程來解決這個問題。
大多數(shù)情況下,多線程耗時操作會與UI進行交互,比如:顯示進度、加載等待。。。讓用戶明確知道目前的狀態(tài),并對結(jié)果有一個直觀的預(yù)期,甚至有趣巧妙的設(shè)計,能讓用戶愛上等待,把等待看成一件很美好的事。
1、多線程操作UI界面的示例
下面,是一個使用多線程操作UI界面的示例 - 更新進度條,采用子類化QThread的方式。與此同時,分享在此過程中有可能遇到的問題及解決方法。

首先創(chuàng)建QtGui應(yīng)用,工程名稱為“myThreadBar”,類名選擇“QMainWindow”,其他選項保持默認即可。再添加一個名稱為WorkerThread的頭文件,定義一個WorkerThread類,讓其繼承自QThread,并重寫run()函數(shù),修改workerthread.h文件如下:
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H
#include <QThread>
#include <QDebug>
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;
// 準(zhǔn)備更新
emit resultReady(nValue);
}
}
signals:
void resultReady(int value);
};
#endif // WORKERTHREAD_H通過在run()函數(shù)中調(diào)用msleep(50),線程會每隔50毫秒讓當(dāng)前的進度值加1,然后發(fā)射一個resultReady()信號,其余時間什么都不做。在這段空閑時間,線程不占用任何的系統(tǒng)資源。當(dāng)休眠時間結(jié)束,線程就會獲得CPU時鐘,將繼續(xù)執(zhí)行它的指令。
再在mainwindow.ui上添加一個按鈕和進度條部件,然后mainwindow.h修改如下:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "workerthread.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
// 更新進度
void handleResults(int value);
// 開啟線程
void startThread();
private:
Ui::MainWindow *ui;
WorkerThread m_workerThread;
};
#endif // MAINWINDOW_H然后mainwindow.cpp修改如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
qDebug() << "Main Thread : " << QThread::currentThreadId();
// 連接信號槽
this->connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(startThread()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::handleResults(int value)
{
qDebug() << "Handle Thread : " << QThread::currentThreadId();
ui->progressBar->setValue(value);
}
void MainWindow::startThread()
{
WorkerThread *workerThread = new WorkerThread(this);
this->connect(workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
// 線程結(jié)束后,自動銷毀
this->connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
workerThread->start();
}由于信號與槽連接類型默認為“Qt::AutoConnection”,在這里相當(dāng)于“Qt::QueuedConnection”。也就是說,槽函數(shù)在接收者的線程(主線程)中執(zhí)行。
執(zhí)行程序,“應(yīng)用程序輸出”窗口輸出如下:
Main Thread : 0x3140
Worker Thread : 0x3140
Worker Run Thread : 0x2588
Handle Thread : 0x3140
顯然,UI界面、Worker構(gòu)造函數(shù)、槽函數(shù)處于同一線程(主線程),而run()函數(shù)處于另一線程(次線程)。
2、避免多次connect
當(dāng)多次點擊“開始”按鈕的時候,就會多次connect(),從而啟動多個線程,同時更新進度條。為了避免這個問題,我們先在mainwindow.h上添加私有成員變量"WorkerThread m_workerThread;",然后修改mainwindow.cpp如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 連接信號槽
this->connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(startThread()));
this->connect(&m_workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::handleResults(int value)
{
qDebug() << "Handle Thread : " << QThread::currentThreadId();
ui->progressBar->setValue(value);
}
void MainWindow::startThread()
{
if (!m_workerThread.isRunning())
m_workerThread.start();
}不再在startThread()函數(shù)內(nèi)創(chuàng)建WorkerThread對象指針,而是定義私有成員變量,再將connect添加在構(gòu)造函數(shù)中,保證了信號槽的正常連接。在線程start()之前,可以使用isFinished()和isRunning()來查詢線程的狀態(tài),判斷線程是否正在運行,以確保線程的正常啟動。
3、優(yōu)雅地結(jié)束線程的兩種方法
如果一個線程運行完成,就會結(jié)束??珊芏嗲闆r并非這么簡單,由于某種特殊原因,當(dāng)線程還未執(zhí)行完時,我們就想中止它。
不恰當(dāng)?shù)闹兄雇鶗鹨恍┪粗e誤。比如:當(dāng)關(guān)閉主界面的時候,很有可能次線程正在運行,這時,就會出現(xiàn)如下提示:
QThread: Destroyed while thread is still running
這是因為次線程還在運行,就結(jié)束了UI主線程,導(dǎo)致事件循環(huán)結(jié)束。這個問題在使用線程的過程中經(jīng)常遇到,尤其是耗時操作。大多數(shù)情況下,當(dāng)程序退出時,次線程也許會正常退出。這時,雖然抱著僥幸心理,但隱患依然存在,也許在極少數(shù)情況下,就會出現(xiàn)Crash。
所以,我們應(yīng)該采取合理的措施來優(yōu)雅地結(jié)束線程,一般思路:
- 發(fā)起線程退出操作,調(diào)用quit()或exit()。
- 等待線程完全停止,刪除創(chuàng)建在堆上的對象。
- 適當(dāng)?shù)氖褂脀ait()(用于等待線程的退出)和合理的算法。
方法一
這種方式是Qt4.x中比較常用的,主要是利用“QMutex互斥鎖 + bool成員變量”的方式來保證共享數(shù)據(jù)的安全性。在workerthread.h上繼續(xù)添加互斥鎖、析構(gòu)函數(shù)和stop()函數(shù),修改如下:
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H
#include <QThread>
#include <QMutexLocker>
#include <QDebug>
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()
{
qDebug() << "Worker Stop Thread : " << QThread::currentThreadId();
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;
// 準(zhǔn)備更新
emit resultReady(nValue);
// 檢測是否停止
{
QMutexLocker locker(&m_mutex);
if (m_bStopped)
break;
}
// locker超出范圍并釋放互斥鎖
}
}
signals:
void resultReady(int value);
private:
bool m_bStopped;
QMutex m_mutex;
};
#endif // WORKERTHREAD_H
當(dāng)主窗口被關(guān)閉,其“子對象”WorkerThread也會析構(gòu)調(diào)用stop()函數(shù),使m_bStopped變?yōu)閠rue,則break跳出循環(huán)結(jié)束run()函數(shù),結(jié)束進程。當(dāng)主線程調(diào)用stop()更新m_bStopped的時候,run()函數(shù)也極有可能正在訪問它(這時,他們處于不同的線程),所以存在資源競爭,因此需要加鎖,保證共享數(shù)據(jù)的安全性。
為什么要加鎖?
很簡單,是為了共享數(shù)據(jù)段操作的互斥。避免形成資源競爭的情況(多個線程有可能訪問同一共享資源的情況)。
方法二
Qt5以后,可以使用requestInterruption()、isInterruptionRequested()這兩個函數(shù),使用很方便,修改workerthread.h文件如下:
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H
#include <QThread>
#include <QMutexLocker>
#include <QDebug>
class WorkerThread : public QThread
{
Q_OBJECT
public:
explicit WorkerThread(QObject *parent = nullptr)
: QThread(parent)
{
qDebug() << "Worker Thread : " << QThread::currentThreadId();
}
~WorkerThread()
{
// 請求終止
requestInterruption();
quit();
wait();
}
protected:
virtual void run() Q_DECL_OVERRIDE
{
qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
int nValue = 0;
// 是否請求終止
while (!isInterruptionRequested())
{
while (nValue < 100)
{
// 休眠50毫秒
msleep(50);
++nValue;
// 準(zhǔn)備更新
emit resultReady(nValue);
}
}
}
signals:
void resultReady(int value);
};
#endif // WORKERTHREAD_H在耗時操作中使用isInterruptionRequested()來判斷是否請求終止線程,如果沒有,則一直運行;當(dāng)希望終止線程的時候,調(diào)用requestInterruption()即可。這兩個函數(shù)內(nèi)部也使用了互斥鎖QMutex。
以上就是一文詳解Qt中線程的實際應(yīng)用的詳細內(nèi)容,更多關(guān)于Qt線程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Windows API實現(xiàn)遍歷所有文件并刪除的方法
這篇文章主要介紹了基于Windows API實現(xiàn)遍歷所有文件并刪除的方法,是win32應(yīng)用程序的一個比較典型的文件操作應(yīng)用技巧,需要的朋友可以參考下2015-04-04
vc++實現(xiàn)的tcp socket客戶端和服務(wù)端示例
這篇文章主要介紹了vc++實現(xiàn)的tcp socket客戶端和服務(wù)端示例,需要的朋友可以參考下2014-03-03
C++示例分析內(nèi)聯(lián)函數(shù)與引用變量及函數(shù)重載的使用
為了消除函數(shù)調(diào)用的時空開銷,C++ 提供一種提高效率的方法,即在編譯時將函數(shù)調(diào)用處用函數(shù)體替換,類似于C語言中的宏展開。這種在函數(shù)調(diào)用處直接嵌入函數(shù)體的函數(shù)稱為內(nèi)聯(lián)函數(shù)(Inline Function),又稱內(nèi)嵌函數(shù)或者內(nèi)置函數(shù)2022-08-08

