實例操作 Qt 多線程 后臺創建縮略圖
本文介紹的是實例操作 Qt 多線程 后臺創建縮略圖,不多說,更多資料參考本文末尾,先來看本文內容。
起因是在qtcn上看到有人說創建縮略圖的API執行很慢,并且阻塞了gui的正常運轉,需要改成多線程來實現,但他不清楚如何實現,希望能有個例子。 這位同學的問題揭示了Qt里最需要用到多線程的一個用例,那就是防止一切阻塞GUI線程的操作。 Qt GUI編程有個最基本但很多人不熟悉的原則是所有的操作都不能是阻塞的, 同時所有的操作都不能占用很多CPU, 這個原則是針對GUI線程來說的, 但后者對子線程也同樣適用(這一點我們后邊繼續八)。
熟悉Qt的人都應該有所了解,Qt的圖形處理是基于事件循環的,這一點和大多數的GUI工具庫一致。事件循環簡單來說就是個死循環,里面讀取系統外設的事件,然后見招拆招。 Qt里的Events, Signals,Timer都在事件循環里分發,所以,如果阻塞了事件循環用腳趾頭想想也能知道會有多么嚴重的后果,最明顯的就是界面不刷新,也不能和用戶交互,所以這就要求我們在寫程序的時候不管是事件處理函數還是槽函數里都不能進行占用CPU時間的操作,特別要杜絕阻塞性的操作如串口數據讀取、杜絕長時間的高CPU占用率的操作如死循環和大數據量的計算等。 However,“人在河邊走, 哪能不濕鞋”呢, 總有需要用到這些操作的時候,Qt也提供了解決方案,那就是多線程。
Qt的多線程類有很多,改天開帖詳細介紹,這里只八最核心的QThread類。 該類的用法是Posix多線程的用法的簡化版, 只需要派生一個QThread的子類,實現其中的run虛函數就大功告成, 用的時候創建該類的實例,調用它的start方法,理論上非常簡單。 筆者個人覺得寫多線程的程序首先要去惡補一下多線程的知識, Qt說到底只是一個工具,它的API包裝得再好也得用的人懂得如何去使用才能發揮最好的作用。 Qt的文檔中關于多線程的編程文章挺多, 另外還列出了一系列的推薦讀物, 想把多線程程序寫好的人一定要看。
前面啰嗦了這么多無非是想告訴大家,寫多線程的程序單靠Qt的知識是不夠的(更別說有些人還沒用過Qt呢),多線程的知識更加重要。 而且單靠一兩篇簡單的文章是學不明白的,你看人家多線程編程能寫一大章書呢!好了,強調一下背景知識的重要性,下面要提供一個最簡單的Qt多線程的例子程序,它的功能是打開多個圖片文件之后創建線程執行圖片縮放的操作,創建圖片的縮略圖,完成后子線程會把縮略圖傳給GUI線程,由GUI線程創建Label控件來顯示縮略圖。各位看官注意了,這個例子很簡單,和多線程相關的部分其實很少,沒有用到Mutex也沒涉及并發等等概念, 單一的演示如何防止大數據量的計算阻塞GUI線程。
例子的源碼見附件,這里先列出一些關鍵的流程和代碼, 方便大家理解:
第一步:打開圖片文件
用QFileDialog::getOpenFileNames獲得圖片文件名的StringList
第二步:遍歷List創建線程
- void MainWin::createThumbnail(const QString& filename)
- {
- QThread* thread = new ThumbnailThread(filename, 10 - waitseconds);
- connect(thread, SIGNAL(thumbnailFinished(QImage)), this, SLOT(addThumbnail(QImage)));
- connect(thread, SIGNAL(thumbnailFailed(const QString)), this, SLOT(showError(const QString)));
- connect(thread, SIGNAL(finished()), this, SLOT(deleteThread()));
- thread->start();
- }
finished是QThread類自帶的信號,在線程結束執行時發出; 其他兩個信號是自定義信號, 用于在GUI線程和子線程之間傳遞數據。
第三步:子線程內創建縮略圖
使用了QImage的scaled方法做圖片縮放。
- void ThumbnailThread::run()
- {
- if( bigpm.isNull())
- {
- emit thumbnailFailed(pmfilename);
- }
- else
- {
- smallpm = bigpm.scaled(TN_WIDTH, TN_HEIGHT, Qt::KeepAspectRatio);
- emit thumbnailFinished(smallpm);
- }
- }
第四步:GUI線程處理thumbnailFinished信號
為每個圖片創建一個Label, 將之加到預先定義好的GridLayout中
- void MainWin::addThumbnail(QImage smallpm)
- {
- static int i = 0;
- static int j = 0;
- QLabel* label = new QLabel;
- label->setPixmap(QPixmap::fromImage(smallpm));
- QGridLayout* gl = qobject_cast(previewwidget->layout());
- gl->addWidget(label, j, i);
- label->show();
- qWarning() << "Label:" <isVisible();
- i++;
- if( i > previewwidget->width() / smallpm.width())
- {
- i = 0;
- j ++;
- }
- }
關鍵點提示:
Qt的signal可跨線程傳遞, 但要注意slot執行的線程。 connect時可以在第五個參數的位置指定連接的屬性(這里用了默認值), 如DirectConnection表示在connect函數執行的線程里執行slot, QueuedConnection表示slot執行在接受者所在的線程,(還有其他選項參看文檔)本例中的slot都執行在GUI線程中。
在收到finished信號后要清除執行完畢的線程, 為了防止刪除后對線程實例的訪問這里用了個deleteLater方法 — 在大家都不再訪問thread實例時再刪除。
Qt中所有和GUI相關的操作都要放在GUI線程里執行,包括所有Widget的創建和訪問,QPixmap的類也算GUI的類, 所以本例中只能用QImage來處理圖片。
這個例子其實用一個子線程就夠了,給每張圖片創建一個線程有點浪費。例子里有些為了測試需要加的代碼可能會影響閱讀(比如那個waitseconds),請大家自動忽略。 比如為了更好的看到效果,給線程里加了個sleep延緩處理圖片的速度…
原文鏈接:http://www.cuteqt.com/blog/?p=547
小結:關于實例操作 Qt 多線程 后臺創建縮略圖 的內容介紹完了,希望本文對你有所幫助!更多關于多線程的內容請參考編輯推薦。