【轉】Qt事件循環與線程 二

轉自:http://blog.csdn.net/changsheng230/article/details/6153449html

續上文:http://blog.csdn.net/changsheng230/archive/2010/12/27/6101232.aspxgit

 

因爲最近工做比較忙,出了趟差,仍是把這篇長文、好文翻譯出來了,以饗讀者。同時也是本身很好的消化、學習過程web

Qt 線程類

Qt對線程的支持已經有不少年了(發佈於2000年九月22日的Qt2.2引入了QThread類),Qt 4.0版本的release則對其全部所支持平臺默認地是對多線程支持的。(固然你也能夠關掉對線程的支持,參見這裏)。如今Qt提供了很多類用於處理線程,讓你咱們首先預覽一下:編程

QThread

QThread 是Qt中一個對線程支持的核心的底層類。 每一個線程對象表明了一個運行的線程。因爲Qt的跨平臺特性,QThread成功隱藏了全部在不一樣操做系統裏使用線程的平臺相關性代碼。設計模式

爲了運用QThread從而讓代碼在一個線程裏運行,咱們能夠建立一個QThread的子類,並重載QThread::run() 方法:瀏覽器

 

 

  1. class Thread : public QThread {  
  2. protected:  
  3. void run() {  
  4. /* your thread implementation goes here */  
  5. }  
  6. };  

 

接着,咱們可使用:緩存

 

  1. class Thread : public QThread {  
  2. protected:  
  3. void run() {  
  4. /* your thread implementation goes here */  
  5. }  
  6. };  

 

 


來真正的啓動一個新的線程。 請注意,Qt 4.4版本以後,QThread再也不支持抽象類;如今虛函數QThread::run()其實是簡單調用了QThread::exec(),而它啓動了線程的事件循環。(更多信息見後文)安全

QRunnable 和 QThreadPool

QRunnable [doc.qt.nokia.com] 是一種輕量級的、以「run and forget」方式來在另外一個線程開啓任務的抽象類,爲了實現這一功能,咱們所須要作的所有事情是派生QRunnable 類,並實現純虛函數方法run()服務器

 

  1. class Task : public QRunnable {  
  2. public:  
  3. void run() {  
  4. /* your runnable implementation goes here */  
  5. }  
  6. };  

 

事實上,咱們是使用QThreadPool 類來運行一個QRunnable 對象,它維護了一個線程池。經過調用QThreadPool::start(runnable) ,咱們把一個QRunnable 放入了QThreadPool的運行隊列中;只要線程是可見得,QRunnable 將會被拾起而且在那個線程裏運行。儘管全部的Qt應用程序都有一個全局的線程池,且它是經過調用 QThreadPool::globalInstance()可見得,但咱們老是顯式地建立並管理一個私有的QThreadPool 實例。網絡

請注意,QRunnable 並非一個QObject類,它並無一個內置的與其餘組件顯式通信的方法。你必須使用底層的線程原語(好比收集結構的枷鎖保護隊列等)來親自編寫代碼。

QtConcurrent

QtConcurrent 是一個構建在QThreadPool之上的上層API,它用於處理最普通的並行計算模式:map [en.wikipedia.org], reduce [en.wikipedia.org], and filter [en.wikipedia.org] 。同時,QtConcurrent::run()方法提供了一種便於在另外一個線程運行一個函數的方法。

不像QThread 以及QRunnable,QtConcurrent 沒有要求咱們使用底層的同步原語,QtConcurrent 全部的方法會返回一個QFuture 對象,它包含告終果並且能夠用來查詢線程計算的狀態(它的進度),從而暫停、繼續、取消計算。QFutureWatcher 能夠用來監聽一個QFuture 進度,而且經過信號和槽與之交互(注意QFuture是一個基於數值的類,它並無繼承自QObject).

功能比較

 

/ QThread QRunnable QtConcurrent1
High level API
Job-oriented
Builtin support for pause/resume/cancel
Can run at a different priority
Can run an event loop

 

線程與QObjects

線程的事件循環

咱們在上文中已經討論了事件循環,咱們可能理所固然地認爲在Qt的應用程序中只有一個事件循環,但事實並非這樣:QThread對象在它們所表明的線程中開啓了新的事件循環。所以,咱們說main 事件循環是由調用main()的線程經過QCoreApplication::exec() 建立的。 它也被稱作是GUI線程,由於它是界面相關操做惟一容許的進程。一個QThread的局部事件循環能夠經過調用QThread::exec() 來開啓(它包含在run()方法的內部)

 

  1. class Thread : public QThread {  
  2. protected:  
  3. void run() {  
  4. /* ... initialize ... */  
  5. exec();  
  6. }  
  7. };  

 

正如咱們以前所提到的,自從Qt 4.4 的QThread::run() 方法再也不是一個純虛函數,它調用了QThread::exec()。就像QCoreApplication,QThread 也有QThread::quit() 和QThread::exit()來中止事件循環。

一個線程的事件循環爲駐足在該線程中的全部QObjects派發了全部事件,其中包括在這個線程中建立的全部對象,或是移植到這個線程中的對象。咱們說一個QObject的線程依附性(thread affinity)是指某一個線程,該對象駐足在該線程內。咱們在任什麼時候間均可以經過調用QObject::thread()來查詢線程依附性,它適用於在QThread對象構造函數中構建的對象。

 

  1. class MyThread : public QThread  
  2. {  
  3. public:  
  4. MyThread()  
  5. {  
  6. otherObj = new QObject;  
  7. }     
  8. private:  
  9. QObject obj;  
  10. QObject *otherObj;  
  11. QScopedPointer<QObject> yetAnotherObj;  
  12. };  

 

如上述代碼,咱們在建立了MyThread 對象後,obj, otherObj, yetAnotherObj 的線程依附性是怎麼樣的?要回答這個問題,咱們必需要看一下建立他們的線程:是這個運行MyThread 構造函數的線程建立了他們。所以,這三個對象並沒有駐足在MyThread 線程,而是駐足在建立MyThread 實例的線程中。

要注意的是在QCoreApplication 對象以前建立的QObjects沒有依附於某一個線程。所以,沒有人會爲它們作事件派發處理。(換句話說,QCoreApplication 構建了表明主線程的QThread 對象)

咱們可使用線程安全的QCoreApplication::postEvent() 方法來爲某個對象分發事件。它將把事件加入到對象所駐足的線程事件隊列中。所以,除非事件對象依附的線程有一個正在運行的事件循環,不然事件不會被派發。

理解QObject和它全部的子類不是線程安全的(儘管是可重入的)很是重要;所以,除非你序列化對象內部數據 全部可訪問的接口、數據,不然你不能讓多個線程同一時刻訪問相同的QObject(好比,用一個鎖來保護)。請注意,儘管你能夠從另外一個線程訪問對象,但 是該對象此時可能正在處理它所駐足的線程事件循環派發給它的事件! 基於這種緣由,你不能從另外一個線程去刪除一個QObject,必定要使用QObject::deleteLater(),它會Post一個事件,目標刪除 對象最終會在它所生存的線程中被刪除。(譯者注:QObject::deleteLater做用是,當控制流回到該對象所依附的線程事件循環時,該對象纔會被「本」線程中刪除)。

此外,QWidget 和它全部的子類,以及全部與GUI相關的類(即使不是基於QObject的,像QPixmap)並非可重入的。它們必須專屬於GUI線程。

咱們能夠經過調用QObject::moveToThread()來改變一個QObject的依附性;它將改變這個對象以及它的孩子們的依附性。由於QObject不是線程安全的,咱們必須在對象所駐足的線程中使用此函數;也就是說,你只能將對象從它所駐足的線程中推送到其餘線程中,而不能從其餘線程中回來。此外,Qt要求一個QObject的孩子必須與它們的雙親駐足在同一個線程中。這意味着:

  • 你不能使用QObject::moveToThread()做用於有雙親的對象;
  • 你千萬不要在一個線程中建立對象的同時把QThread對象本身做爲它的雙親。 (譯者注:二者不在同一個線程中):

 

  1. class Thread : public QThread {  
  2. void run() {  
  3. QObject obj = new QObject(this); // WRONG!!!  
  4. }  
  5. };  

 

這是由於,QThread 對象駐足在另外一個線程中,即QThread 對象它本身被建立的那個線程中。

Qt一樣要求全部的對象應該在表明該線程的QThread對象銷燬以前得以刪除;實現這一點並不難:只要咱們全部的對象是在QThread::run() 方法中建立便可。(譯者注:run函數的局部變量,函數返回時得以銷燬)。

跨線程的信號與槽

接着上面討論的,咱們如何應用駐足在其餘線程裏的QObject方法呢?Qt提供了一種很是友好並且乾淨的解決方案:向事件隊列post一個事件, 事件的處理將以調用咱們所感興趣的方法爲主(固然這須要線程有一個正在運行的事件循環)。而觸發機制的實現是由moc提供的內省方法實現的(譯者注:有關 內省的討論請參見個人另外一篇文章Qt的內省機制剖析):所以,只有信號、槽以及被標記成Q_INVOKABLE的方法纔可以被其它線程所觸發調用。

靜態方法QMetaObject::invokeMethod() 爲咱們作了以下工做:

 

  1. QMetaObject::invokeMethod(object, "methodName",  
  2. Qt::QueuedConnection,  
  3. Q_ARG(type1, arg1),  
  4. Q_ARG(type2, arg2));  

 

請注意,由於上面所示的參數須要被在構建事件時進行硬拷貝,參數的自定義型別所對應的類須要提供一個共有的構造函數、析構函數以及拷貝構造函數。並且必須使用註冊Qt型別系統所提供的qRegisterMetaType() 方法來註冊這一自定義型別。

跨線程的信號槽的工做方式相相似。當咱們把信號鏈接到一個槽的時候,QObject::connect的第五個可選輸入參數用來特化這一鏈接類型:

  • direct connection 是指:發起信號的線程會直接觸發其所鏈接的槽;
  • queued connection 是指:一個事件被派發到接收者所在的線程中,在這裏,事件循環會以後的某一時間將該事件拾起並引發槽的調用;
  • blocking queued connection 與queued connection的區別在於,發送者的線程會被阻塞,直至接收者所在線程的事件循環處理髮送者發送(入棧)的事件,當鏈接信號的槽被觸發後,阻塞被解除;
  • automatic connection (缺省默認參數) 是指: 若是接收者所依附的線程和當前線程是同一個線程,direct connection會被使用。不然使用queued connection。

請注意,在上述四種鏈接方式當中,發送對象駐足於哪個線程並不重要!對於automatic connection,Qt會檢查觸發信號的線程,而且與接收者所駐足的線程相比較從而決定到底使用哪種鏈接類型。特別要指出的是:當前的Qt文檔的聲明(4.7.1) 是錯誤的:

若是發射者和接受者在同一線程,其行爲與Direct Connection相同;,若是發射者和接受者不在同一線程,其行爲Queued Connection相同

由於,發送者對象的線程依附性在這裏可有可無。舉例子說明

 

  1. class Thread : public QThread  
  2. {  
  3. Q_OBJECT  
  4. signals:  
  5. void aSignal();  
  6. protected:  
  7. void run() {  
  8. emit aSignal();  
  9. }  
  10. };  
  11. /* ... */  
  12. Thread thread;  
  13. Object obj;  
  14. QObject::connect(&thread, SIGNAL(aSignal()), &obj, SLOT(aSlot()));  
  15. thread.start();  

 

如上述代碼,信號aSignal() 將在一個新的線程裏被髮射(由線程對象所表明);由於它並非Object 對象駐足的線程,因此儘管Thread對象thread與Object對象obj在同一個線程,但仍然是queued connection被使用。

譯者注:這裏做者分析的很透徹,但願讀者仔細揣摩Qt文檔的這個錯誤。 也就是說 發送者對象自己在哪個線程對與信號槽鏈接類型不起任何做用,起到決定做用的是接收者對象所駐足的線程以及發射信號(該信號與接受者鏈接)的線程是否是在 同一個線程,本例中aSignal()在新的線程中被髮射,因此採用queued connection)。

另一個常見的錯誤以下:

 

[c-sharp] view plain copy
  1. class Thread : public QThread  
  2. {  
  3. Q_OBJECT  
  4. slots:  
  5. void aSlot() {  
  6. /* ... */  
  7. }  
  8. protected:  
  9. void run() {  
  10. /* ... */  
  11. }  
  12. };  
  13. /* ... */  
  14. Thread thread;  
  15. Object obj;  
  16. QObject::connect(&obj, SIGNAL(aSignal()), &thread, SLOT(aSlot()));  
  17. thread.start();  
  18. obj.emitSignal();  

 

當「obj」發射了一個aSignal()信號是,哪一種鏈接將被使用呢?你也許已經猜到了:direct connection。這 是由於Thread對象實在發射該信號的線程中生存。在aSlot()槽裏,咱們可能接着去訪問線程裏的一些成員變量,然而這些成員變量可能同時正在被 run()方法訪問:這但是致使完美災難的祕訣。可能你常常在論壇、博客裏面找到的解決方案是在線程的構造函數里加一個 moveToThread(this)方法。

class Thread : public QThread {

Q_OBJECT

public:

Thread() {

moveToThread(this); // 錯誤

}

/* ... */

};

(譯註:moveToThread(this)

這樣作確實能夠工做(由於如今線程對象的依附性已經發生了改變),但這是一個很是很差的設計。這裏的錯誤在於咱們正在誤解線程對象的目的(QThread子類):QThread對象們不是線程;他們是圍繞在新產生的線程周圍用於控制管理新線程的對象,所以,它們應該用在另外一個線程(每每在它們所駐足的那一個線程)

一個比較好並且可以獲得相同結果的作法是將「工做」部分從「控制」部分剝離出來,也就是說,寫一個QObject子類並使用QObject::moveToThread()方法來改變它的線程依附性:

 

  1. class Worker : public QObject  
  2. {  
  3. Q_OBJECT  
  4. public slots:  
  5. void doWork() {  
  6. /* ... */  
  7. }  
  8. };  
  9. /* ... */  
  10. QThread thread;  
  11. Worker worker;  
  12. connect(obj, SIGNAL(workReady()), &worker, SLOT(doWork()));  
  13. worker.moveToThread(&thread);  
  14. thread.start();  

 

 

我應該何時使用線程

當你不得不使用一個阻塞式API時

當你須要(經過信號和槽,或者是事件、回調函數)使用一個沒有提供非阻塞式API的庫或者代碼時,爲了阻止凍結事件循環的惟一可行的解決方案是開啓一個進程或者線程。因爲建立一個新的進程的開銷顯然要比開啓一個線程的開銷大,後者每每是最多見的一種選擇。

這種API的一個很好的例子是地址解析 方法(只是想說咱們並不許備談論蹩腳的第三方API, 地址解析方法它是每一個C庫都要包含的),它負責將主機名轉化爲地址。這個過程涉及到啓動一個查詢(一般是遠程的)系統:域名系統或者叫DNS。儘管一般情 況下響應會在瞬間發生,但遠程服務器可能會失敗:一些數據包可能會丟失,網絡鏈接可能斷開等等。簡而言之,咱們也許要等待幾十秒才能獲得查詢的響應。

UNIX系統可見的標準API只有阻塞式的(不只過期的gethostbyname(3)是阻塞式的,並且更新的getservbyname(3) 以及getaddrinfo(3)也是阻塞式的)。QHostInfo [doc.qt.nokia.com],  它是一個負責處理域名查找的Qt類,該類使用了QThreadPool 從而使得查詢能夠在後臺進行)(參見here [qt.gitorious.com]);若是屏蔽了多線程支持,它將切換回到阻塞式API).

另外一個簡單的例子是圖像裝載和放大。QImageReader [doc.qt.nokia.com]QImage [doc.qt.nokia.com]僅僅提供了阻塞式方法來從一個設備讀取圖像,或者放大圖像到一個不一樣的分辨率。若是你正在處理一個很是大的圖像,這些處理會持續數(十)秒。

當你想擴展至多核

多線程容許你的程序利用多核系統的優點。由於每一個線程都是被操做系統獨立調度的,所以若是你的應用運行在這樣多核機器上,調度器極可能同時在不一樣的處理器上運行每一個線程。

例如,考慮到一個經過圖像集生成縮略圖的應用。一個_n_ threads的線程農場(也就是說,一個有着固定 數量線程的線程池),在系統中可見的CPU運行一個線程(可參見QThread::idealThreadCount()),能夠將縮小圖像至縮略圖的工 做交付給全部的進程,從而有效地提升了並行加速比,它與處理器的數量成線性關係。(簡單的講,咱們認爲CPU正成爲一個瓶頸)。

何時你可能不想別人阻塞

這是一個很高級的話題,你能夠忽略該小節。一個比較好的例子來自於Webkit裏使用的QNetworkAccessManager 。Webkit是一個時髦的瀏覽器引擎,也就是說,它是一組用於處理網頁的佈局和顯示的類集合。使用Webkit的Qt widget是QWebView。

QNetworkAccessManager 是一個用於處理HTTP任何請求和響應的Qt類,咱們能夠把它看成一個web瀏覽器的網絡引擎;全部的網絡訪問被同一個QNetworkAccessManager 以及它的QNetworkReplys 駐足的線程所處理。

儘管在網絡處理時不使用線程是一個很好的主意,它也有一個很大的缺點:若是你沒有從socket中儘快地讀取數據,內核的緩存將會被填滿,數據包可能開始丟失並且傳輸速率也將迅速降低。

Sokcet活動(即,從一個socket讀取一些數據的可見性)由Qt的事件循環管理。阻塞事件循環所以會致使傳輸性能的損失,由於沒有人會被通知將有數據能夠讀取(從而沒人會去讀數據)。

但究竟什麼會阻塞事件循環呢?使人沮喪地回答: WebKit它本身!只要有數據被接收到,WebKit便用其來佈局網頁。不幸地是,佈局處理過程至關複雜,並且開銷巨大。所以,它阻塞事件循環的一小段 時間足以影響到正在進行地傳輸(寬帶鏈接這裏起到了做用,在短短几秒內就可填滿內核緩存)。

總結一下上述所發生的事情:

  • WebKit提出了一個請求;
  • 一些響應數據開始到達;
  • WebKit開始使用接收到的數據佈局網頁,從而阻塞了事件循環;
  • 數據被OS接受,但沒有一個正在運行的事件循環爲之派發,因此並無被QNetworkAccessManager sockets所讀取;
  • 內核緩存將被填滿,傳輸將變慢。

網頁的整體裝載時間因其自發引發的傳輸速率下降而變得愈來愈壞。

諾基亞的工程師正在試驗一個支持多線程的QNetworkAccessManager來解決這個問題。請注意由於 QNetworkAccessManagers 和QNetworkReplys 是QObjects,他們不是線程安全的,所以你不能簡單地將他們移到另外一個線程中而且繼續在你的線程中使用他們,緣由在於,因爲事件將被隨後線程的事件 循環所派發,他們可能同時被兩個線程訪問:你本身的線程以及已經它們駐足的線程。

 

是麼時候不須要使用線程

If you think you need threads then your processes are too fat.—Rob Pike

計時器

這也許是線程濫用最壞的一種形式。若是咱們不得不重複調用一個方法(好比每秒),許多人會這樣作:

 

  1. // 很是之錯誤  
  2. while (condition) {  
  3. doWork();  
  4. sleep(1); // this is sleep(3) from the C library  
  5. }  

 

而後他們發現這會阻塞事件循環,所以決定引入線程:

 

  1. // 錯誤  
  2. class Thread : public QThread {  
  3. protected:  
  4. void run() {  
  5. while (condition) {  
  6. // notice that "condition" may also need volatiness and mutex protection  
  7. // if we modify it from other threads (!)  
  8. doWork();  
  9. sleep(1); // this is QThread::sleep()  
  10. }  
  11. }  
  12. };  

 

一個更好也更簡單的得到相同效果的方法是使用timers,即一個QTimer[doc.qt.nokia.com]對象,並設置一秒的超時時間,並讓doWork方法成爲它的槽:

 

  1. class Worker : public QObject  
  2. {  
  3. Q_OBJECT  
  4. public:  
  5. Worker() {  
  6. connect(&timer, SIGNAL(timeout()), this, SLOT(doWork()));  
  7. timer.start(1000);  
  8. }  
  9. private slots:  
  10. void doWork() {  
  11. /* ... */  
  12. }  
  13. private:  
  14. QTimer timer;  
  15. };  

 

全部咱們須要作的就是運行一個事件循環,而後doWork()方法將會被每隔秒鐘調用一次。

網絡/狀態機

一個處理網絡操做很是之常見的設計模式以下:

 

  1. socket->connect(host);  
  2. socket->waitForConnected();  
  3. data = getData();  
  4. socket->write(data);  
  5. socket->waitForBytesWritten();  
  6. socket->waitForReadyRead();  
  7. socket->read(response);  
  8. reply = process(response);  
  9. socket->write(reply);  
  10. socket->waitForBytesWritten();  
  11. /* ... and so on ... */  

 

不用多說,各類各樣的waitFor*()函數阻塞了調用者使其沒法返回到事件循環,UI被凍結等等。請注意上面的這段代碼並無考慮到錯誤處理,不然它會更加地笨重。這個設計中很是錯誤的地方是咱們正在忘卻網絡編程是異步的設計,若是咱們構建一個同步的處理方法,則是本身給本身找麻煩。爲了解決這個問題,許多人簡單得將這些代碼移到另外一個線程中。

另外一個更加抽象的例子:

 

  1. result = process_one_thing();  
  2. if (result->something())  
  3. process_this();  
  4. else  
  5. process_that();  
  6. wait_for_user_input();  
  7. input = read_user_input();  
  8. process_user_input(input);  
  9. /* ... */  

 

它多少反映了網絡編程相同的陷阱。

 

讓咱們回過頭來從更高的角度來想一下咱們這裏正在構建的代碼:咱們想創造一個狀態機,用以反映某類的輸入並相對應的做某些動做。好比,上面的這段網絡代碼,咱們可能想作以下這些事情:

  • 空閒→ 正在鏈接 (當調用connectToHost());
  • 正在鏈接→ 已經鏈接(當connected() 信號被髮射);
  • 已經鏈接→ 發送錄入數據 (當咱們發送錄入的數據給服務器);
  • 發送錄入數據 → 錄入 (服務器響應一個ACK)
  • 發送錄入數據→ 錄入錯誤(服務器響應一個NACK)

以此類推。

如今,有不少種方式來構建狀態機(Qt甚至提供了QStateMachine[doc.qt.nokia.com]類),最簡單的方式是用一個枚舉值(及,一個整數)來記憶當前的狀態。咱們能夠這樣重寫如下上面的代碼:

 

  1. class Object : public QObject  
  2. {  
  3. Q_OBJECT  
  4. enum State {  
  5. State1, State2, State3 /* and so on */  
  6. };  
  7. State state;  
  8. public:  
  9. Object() : state(State1)  
  10. {  
  11. connect(source, SIGNAL(ready()), this, SLOT(doWork()));  
  12. }  
  13. private slots:  
  14. void doWork() {  
  15. switch (state) {  
  16. case State1:  
  17. /* ... */  
  18. state = State2;  
  19. break;  
  20. case State2:  
  21. /* ... */  
  22. state = State3;  
  23. break;  
  24. /* etc. */  
  25. }  
  26. }  
  27. };  

 

那麼「souce」對象和它的信號「ready()」 到底是什麼? 咱們想讓它們是什麼就是什麼:好比說,在這個例子中,咱們可能想把咱們的槽鏈接到socket的 QAbstractSocket::connected() 以及QIODevice::readyRead() 信號中,固然,咱們也能夠簡單地在咱們的用例中加更多的槽(好比一個槽用於處理錯誤狀況,它將會被QAbstractSocket::error() 信號所通知)。這是一個真正的異步的,信號驅動的設計!

分解任務拆成不一樣的塊

假如咱們有一個開銷很大的計算,它不可以輕易的移到另外一個線程中(或者說它根本不能被移動,舉個例子,它必須運行在GUI線程中)。若是咱們能將計算拆分紅小的塊,咱們就能返回到事件循環,讓它來派發事件,並讓它激活處理下一個塊相應的函數。若是咱們還記得queued connections是怎麼實現的,那麼會以爲這是很容易可以作到的:一個事件派發到接收者所駐足的線程的事件循環;當事件被傳遞,相應的槽隨之被激活。

咱們可使用特化QMetaObject::invokeMethod() 的激活類型爲Qt::QueuedConnection 來獲得相同的結果;這須要函數是可激活的。所以它須要一個槽或者用Q_INVOKABLE宏來標識。若是咱們同時想給函數中傳入參數,他們須要使用Qt元 對象類型系統裏的qRegisterMetaType()進行註冊。請看下面這段代碼:

 

  1. class Worker : public QObject  
  2. {  
  3. Q_OBJECT  
  4. public slots:  
  5. void startProcessing()  
  6. {  
  7. processItem(0);  
  8. }  
  9. void processItem(int index)  
  10. {  
  11. /* process items[index] ... */  
  12. if (index < numberOfItems)  
  13. QMetaObject::invokeMethod(this,  
  14. "processItem",  
  15. Qt::QueuedConnection,  
  16. Q_ARG(int, index + 1));  
  17. }  
  18. };  

 

 

由於並無引入多線程,因此暫停/進行/取消這樣的計算並收集回結果變得簡單。(結束

 

原文出處:

http://developer.qt.nokia.com/wiki/ThreadsEventsQObjects

請尊重原創做品和譯文。轉載請保持文章完整性,並以超連接形式註明原始做者主站點地址,方便其餘朋友提問和指正。

相關文章
相關標籤/搜索