生活随笔
收集整理的這篇文章主要介紹了
QT之深入理解QThread
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
QT之深入理解QThread
理解QThread之前需要了解下QThread類,QThread擁有的資源如下(摘錄于QT 5.1 幫助文檔):
在以上資源中,本文重點(diǎn)關(guān)注槽:start();信號(hào):started()、finished();受保護(hù)的方法:run()、exec();
理解QThread
QThread與通常所熟知的線程(thread)有很大出入,在面向過(guò)程的語(yǔ)言中,我們建立一個(gè)線程的同時(shí)會(huì)傳入一個(gè)函數(shù)名,這個(gè)函數(shù)名代表該線程要執(zhí)行的具體代碼(如圖 1 所示)。
圖 1. 我們通常所理解的線程
但是QThread里并沒(méi)有線程的具體代碼,QThread只是一個(gè)接口而已,目的是為操作系統(tǒng)提供一個(gè)用于線程調(diào)度的“句柄”。這個(gè)“句柄”即是QThread的入口(如圖 2 所示)。
圖 2. QThread是“面向?qū)ο蟮摹?
QThread的入口多種多樣,可以是槽函數(shù),也可能是某個(gè)事件處理函數(shù),但是由于是由系統(tǒng)調(diào)度的,因此這些函數(shù)的“準(zhǔn)確”執(zhí)行時(shí)刻是無(wú)法預(yù)知的。
QThread的出口是finished()信號(hào)。
作為線程,QThread會(huì)毫不猶豫的為自己創(chuàng)建一個(gè)運(yùn)行空間,一個(gè)單獨(dú)的執(zhí)行線索,一個(gè)新的線程,但是翻閱QThread所擁有的資源,我們找不到傳入函數(shù)名的地方,因此我們仿佛無(wú)法為這個(gè)新創(chuàng)建的線程提供具體的執(zhí)行代碼。
很多人因此想到了run()方法,因而繼承QThread函數(shù),并將自己的代碼寫(xiě)在run()方法中,往往要求run()方法不可以立刻退出,因此加入循環(huán)體和wait()方法,有時(shí)候?yàn)榱隧憫?yīng)事件而調(diào)用exec()進(jìn)行堵塞。但這種做法是不建議的,已有文章指出“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.?”具體參見(jiàn):<http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/>。
? ??那么,QThread真的不能執(zhí)行具體代碼么?如果不是,怎樣將要在新線程中執(zhí)行的程序交付給QThread呢?答案是moveToThread()方法。任何基于QObject類的子類都具有該方法。某個(gè)對(duì)象被moveTo到新線程后,它所具有的槽函數(shù)和事件處理程序都會(huì)被移動(dòng)到新線程所在的運(yùn)行空間中,成為新線程與操作系統(tǒng)之間的接口,即成為了新線程的入口。當(dāng)有與這個(gè)槽連接的信號(hào)或與之相配的事件發(fā)生時(shí),槽函數(shù)和事件處理程序?qū)?huì)在新線程空間中執(zhí)行。
如果只到此為止,那么很容易出現(xiàn)另一個(gè)問(wèn)題,也就是上面連接中所舉的例子。我們?cè)谶@里詳細(xì)說(shuō)明。程序如下:
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(); };
上面這段程序的問(wèn)題在哪兒呢?正如原文所說(shuō):“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).”
總結(jié)起來(lái),問(wèn)題有兩點(diǎn):1.在構(gòu)造函數(shù)中moveToThread(),此時(shí)MyThread還沒(méi)有開(kāi)始運(yùn)行;2.將MyThread移動(dòng)到它自己空間去運(yùn)行后,我們失去了對(duì)MyThread的引用。以上兩點(diǎn)都容易導(dǎo)致非常致命的問(wèn)題。可見(jiàn),我們?yōu)榱俗尨a在新線程中得以執(zhí)行,我們實(shí)在有點(diǎn)兒太“不擇手段”了。
出現(xiàn)以上問(wèn)題的根本原因在于,并沒(méi)有充分理解QThread只是一個(gè)接口的本質(zhì)。那么應(yīng)該如何正確的讓程序在新線程中得以執(zhí)行呢?答案是將需要在新線程中運(yùn)行的對(duì)象moveTo到QThread中,而非繼承QThread并把自身moveTo到新線程空間中。
由此我們提出應(yīng)用QThread的以下幾個(gè)重要原則。
QThread應(yīng)用原則:
1.QThread只是系統(tǒng)執(zhí)行線程的接口而已,并不是用于編寫(xiě)代碼的;
2.在當(dāng)前線程(如:線程A)上下文中創(chuàng)建的對(duì)象屬于當(dāng)前線程,其他線程(如:線程B、C、D...)不可以操作屬于當(dāng)前線程(如:線程A)的對(duì)象;
3.當(dāng)前線程(如:線程A)中基于OBject類的對(duì)象可以被移動(dòng)到其他線程(如:線程B、C、D...);
4.當(dāng)前線程(如:線程A)中基于OBject類的對(duì)象在移動(dòng)到其他線程(如:線程B、C、D...)去執(zhí)行的時(shí)候,要求目標(biāo)線程(如:線程B、C、D...)已經(jīng)開(kāi)始運(yùn)行;
由2可以推出,如果當(dāng)前線程(如:線程A)中,基于OBject類的對(duì)象被移動(dòng)到其他線程(如:線程B、C、D...)之后,該對(duì)象只能由目標(biāo)線程(如:線程B、C、D...)負(fù)責(zé)釋放。
另外,在將信號(hào)與被moveTo到新線程中的對(duì)象所擁有的槽相連接時(shí),需要注意連接的方式。
注意:
信號(hào)與槽的連接方式有:Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection和Qt::BlockingQueuedConnection。
Qt::AutoConnection:是根據(jù)對(duì)象所在線程不同而選擇Qt::DirectConnection或Qt::QueuedConnection;
Qt::DirectConnection:用于同一個(gè)線程當(dāng)中,相當(dāng)于直接函數(shù)調(diào)用,槽函數(shù)執(zhí)行完后才返回;
Qt::QueuedConnection:用于不同的線程當(dāng)中,會(huì)建立一個(gè)隊(duì)列,槽函數(shù)立即返回,而不用等待隊(duì)列中的信號(hào)執(zhí)行完畢;
Qt::BlockingQueuedConnection:也是用于不同線程的,但是又相當(dāng)于函數(shù)調(diào)用,因?yàn)橐鹊讲酆瘮?shù)執(zhí)行完畢才能夠返回。
示例:
在此,提供一個(gè)應(yīng)用QThread的示例,該示例中打開(kāi)一個(gè)串口用于接收數(shù)據(jù),但為了同時(shí)兼顧UI對(duì)用戶的響應(yīng),需要為串口接收程序單獨(dú)建立一個(gè)線程。由于串口對(duì)象被moveTo到了新線程中,因此無(wú)法在UI線程中關(guān)閉串口,因此要用到QThread的finished()信號(hào)。
這只是一個(gè)示例,代碼的編寫(xiě)更注重演示效果,而非其他。
該示例的工程組織如下:
uiwindow.ui文件中窗體為初始化狀態(tài)。
Serial.pro 文件內(nèi)容如下:
--------------------------------------------------------------------------
#------------------------------------------------- # # 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 文件內(nèi)容如下: -------------------------------------------------------------------------- #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 文件內(nèi)容如下: -------------------------------------------------------------------------- #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("無(wú)法打開(kāi)該串口"); 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); // 注意,真正執(zhí)行時(shí) port 與 Serial 在同一個(gè)線程中,因此使用 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(); // 關(guān)閉串口。 } } -------------------------------------------------------------------------- uiwindow.h 文件內(nèi)容如下: -------------------------------------------------------------------------- #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 文件內(nèi)容如下: -------------------------------------------------------------------------- #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 并不在同一個(gè)線程中,因此使用 Qt::QueuedConnection。 connect(&serialThread, SIGNAL(finished()), serial, SLOT(threadFinished()), Qt::DirectConnection); // serialThread 的 finished() 信號(hào)是在新線程中執(zhí)行的,因此此處要使用 Qt::DirectConnection。 serialThread.start(QThread::HighestPriority); // 開(kāi)啟線程,串口接收線程的優(yōu)先級(jí)較高。 serial->moveToThread(&serialThread); // 將串口接受對(duì)象移動(dòng)到新線程中。 serial->port->moveToThread(&serialThread); // 用于接收的 port 一并移入新線程中。 } UIWindow::~UIWindow() { if(serialThread.isRunning()) { serialThread.exit(); // 結(jié)束該線程。 serialThread.wait(); /*while(!serialThread.isFinished()) { ; }*/ } delete ui; } --------------------------------------------------------------------------
http://blog.csdn.net/desert187/article/details/37932999
總結(jié)
以上是生活随笔 為你收集整理的QT之深入理解QThread 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。