QThread的常見特性:
run()是線程的入口,就像main()對於應用程序的作用。QThread中對run()的默認實現調用了exec(),從而創建一個QEventLoop對象,由其處理該線程事件隊列(每一個線程都有一個屬於自己的事件隊列)中的事件。簡單用代碼描述如下:
1 int QThread::exec() 2 { 3 //... 4 QEventLoop eventLoop; 5 int returnCode = eventLoop.exec(); 6 //... 7 return returnCode; 8 } 9 10 int QEventLoop::exec(ProcessEventsFlags flags) 11 { 12 //... 13 while (!d->exit) { 14 while (!posted_event_queue_is_empty) { 15 process_next_posted_event(); 16 } 17 } 18 //... 19 }
由此可見,exec()在其內部不斷做着循環遍歷事件隊列的工作,調用QThread的quit()或exit()方法使停止工作,盡量不要使用terminate(),該方法過於粗暴,造成資源不能釋放,甚至互斥鎖還處於加鎖狀態。
1.舊的使用方式:
這是qt4.6及之前的使用方法,這種方式本沒有什么錯誤,可以處理我們的絕大多數需求。Thread對象本身工作在主線程下,即使調用的t.stop()方法,它也是工作在主線程下,只有run()范圍內的代碼工作在次線程中。
1 class WorkerThread : public QThread
2 {
3 Q_OBJECT
4
5 void run() Q_DECL_OVERRIDE {
6 QString result;
7 emit resultReady(result); // 只發射信號,去真正的業務內容
8 }
9
10 signals:
11 void resultReady(const QString &s);
12 };
13
14 void MyObject::startWorkInAThread()
15 {
16 WorkerThread *workerThread = new WorkerThread(this);
17 connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
18 connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
19 workerThread->start();
20 }
從Qt4.8起,可以釋放運行剛剛結束的線程對象,通過連接finished()信號到QObject::deleteLater()。
2.推薦的使用方式:
這是Qt4.7及以后版本推薦的工作方式。其主要特點就是利用Qt的事件驅動特性,將需要在次線程中處理的業務放在獨立的模塊(類)中,由主線程創建完該對象后,將其移交給指定的線程,且可以將多個類似的對象移交給同一個線程。在這個例子中,信號由主線程的QTimer對象發出,之后Qt會將關聯的事件放到worker所屬線程的事件隊列。由於隊列連接的作用,在不同線程間連接信號和槽是很安全的。
說說connect最后一個參數,連接類型:
1)自動連接(AutoConnection),默認的連接方式,如果信號與槽,也就是發送者與接受者在同一線程,等同於直接連接;如果發送者與接受者處在不同線程,等同於隊列連接。
2)直接連接(DirectConnection),當信號發射時,槽函數立即直接調用。無論槽函數所屬對象在哪個線程,槽函數總在發送者所在線程執行。
3)隊列連接(QueuedConnection),當控制權回到接受者所在線程的事件循環時,槽函數被調用。槽函數在接受者所在線程執行。
1 class Worker : public QObject 2 { 3 Q_OBJECT 4 5 public slots: 6 void doWork(const QString meter) { 7 // ... 8 emit resultReady(result); 9 } 10 void stopWork(){ 11 //... 12 } 13 signals: 14 void resultReady(const QString &result); 15 }; 16 17 class Controller : public QObject 18 { 19 Q_OBJECT 20 21 QThread workerThread; 22 23 public: 24 Controller() { 25 Worker *worker = new Worker; 26 worker->moveToThread(&workerThread); 27 connect(workerThread, &QThread::finished, worker, &QObject::deleteLater); 28 connect(this, &Controller::operate, worker, &Worker::doWork); 29 connect(this, &Controller::kill, worker, &Worker::stopWork); 30 connect(worker, &Worker::resultReady, this, &Controller::handleResults); 31 workerThread.start(); 32 } 33 ~Controller() { 34 workerThread.quit(); 35 workerThread.wait(); 36 } 37 38 public slots: 39 void handleResults(const QString &); 40 41 signals: 42 void operate(const QString &); 43 void kill(); 44 };
使用新方式,子線程中的槽都在子線程中運行,主線程中的槽都在主線程中運行,信號和槽默認使用自動連接(AutoConnection)。值得注意的是,如果槽doWork中有耗時操作,比如說while循環,主線程的信號kill子線程是不會響應的,除非使用直接連接(DirectConnection),connect(this, &Controller::kill, worker, &Worker::stopWork, Qt::DirectConnection);,此時,槽stopWork工作於主線程。
3.GUI界面假死的處理
在GUI程序中,主線程也叫GUI線程,因為它是唯一被允許執行GUI相關操作的線程。對於一些耗時的操作,如果放在主線程中,就是出現界面無法響應的問題。這種問題的解決一種方式是,把這些耗時操作放到次線程中,還有一種比較簡單的方法:在處理耗時操作中頻繁調用QApplication::processEvents()。這個函數告訴Qt去處理那些還沒有被處理的各類事件,然后再把控制權返還給調用者。