筆記:
使用中:子線程要向主線程發送 QMap<QString, QString> 類型的變量
子線程中:
QMap<QString, QString> testMap;
emit testSignal(testMap);
主線程中沒有收到該信號,并有如下信息:
QObject::connect: Cannot queue arguments of type 'QMap<QString,QString>'
(Make sure 'QMap<QString,QString>' is registered using qRegisterMetaType().)
解決方案:
在主界面構造函數中加入以下代碼:
qRegisterMetaType<QMap<QString, QString>>("QMap<QString, QString>");
以下我將分享我的一些淺薄的對moveToThread()的使用心得。
使用線程有兩種方法:
一、
平時我們使用線程的時候一般是繼承QThread,實現它的run()函數,將需要在線程執行的代碼放在run()里邊運行。
{
while(bRun)//如果需要退出線程就將bRun設置為false.
{
qDebug()<<"run thread ID = "<<QThread::currentThreadId();
QThread::usleep(0);//usleep的值設置為0一樣會占滿cpu,但是根據老大測試,并不會使程序變卡。你也可以設置為10.
}
// this->exec();//沒加這個的話線程就會結束,并發出Finsh()信號
}
如果使用這一方法,QThread::quit()沒有效果。因為這個線程根本就不需要事件循環。這種情況想退出,將bRun設置為false或者直接使用QT很不推薦的terminate().
二、使用moveToThread(),因為在Qt4.3(包括)之前,run 是虛函數,必須子類化QThread來實現run函數。而從Qt4.4開始run() 默認調用 QThread::exec() ,線程在調用quit()、exit()或terminat()之前不會退出。這樣一來不需要子類化 QThread 了,只需要子類化一個 QObject 就夠了,這正是被 Bradley T. Hughes(Qt的開發人員)推薦的方法。怎么用呢,使用connect()!!所以下邊要了解信號和槽的關系。等下會分析下connnect的第五個參數。對了,如果moveToThread里執行的函數沒執行完,你是無法通過quit來結束的,必須使用第一種方法:最歹毒的一招mthread->terminate()強制退出。
[cpp] view plain copy
class MyMoveToThreadFunc :public QObject
{
Q_OBJECT
public:
void showObjectThreadID()
{
qDebug()<<"# MyMoveToThreadFunc thread id = "<<QThread::currentThreadId();
}
signals:
void again();
public slots:
void slotOfThread()
{
qDebug()<<" MyMoveToThreadFunc slot thread id = "<<QThread::currentThreadId();
emit again();
}
};
class MyTry:public QObject
{
Q_OBJECT
public:
MyTry();
QThread *mThread;
MyMoveToThreadFunc *mFunc;
};
[cpp] view plain copy
MyTry::MyTry()
{
mThread = new QThread();
qDebug()<<"main thread id = "<<QThread::currentThreadId();
mFunc = new MyMoveToThreadFunc();
mFunc->moveToThread(mThread);</span>
QObject::connect(mThread,SIGNAL(started()),mFunc,SLOT(slotOfThread()));//slot將會在mThread中運行
mFunc->showObjectThreadID()//在主程序運行。
mThread->start();//啟動線程
}
如果要實現事件循環怎么辦?一樣,隨便找個信號連接到slotOfThread(),循環發送就行了。例如,你重載mThread,讓他以第一種循環循環發出信號給mFunc連接也行。記住,直接在主程序調用mFunc的函數,函數還是會是在主程序運行。在這啰嗦一句,子類化QThread的子類,只有在run()函數里才是屬于線程。所以有時候你在子類構造函數創建的實例是不屬于線程創建的,有時候就會提示這種: QObject::startTimer: Timers can only be used with threads started with QThread 。(我還沒測試,別人發的問題認為是這個)。
現在我們來分析下connect()的五個參數。先吃飯,可能明天寫完。
有六種參數:
1
2
3
4
5
6
Qt::AutoConnection
Qt::DirectConnection
Qt::QueuedConnection
Qt::
BlockingQueuedConnection
Qt::UniqueConnection
Qt::AutoCompatConnection
這里面一共有六種方式。
前兩種比較相似,都是同一線程之間連接的方式,不同的是Qt::AutoConnection是系統默認的連接方式。這種方式連接的時候,槽不是馬上被執行的,而是進入一個消息隊列,待到何時執行就不是我們可以知道的了,當信號和槽不是同個線程,會使用第三種QT::QueueConnection的鏈接方式。如果信號和槽是同個線程,調用第二種Qt::DirectConnection鏈接方式。
第二種Qt::DirectConnection是直接連接,也就是只要信號發出直接就到槽去執行,無論槽函數所屬對象在哪個線程,槽函數都在發射信號的線程內執行,一旦使用這種連接,槽將會不在線程執行!。
第三種Qt::QueuedConnection和第四種Qt::BlockingQueuedConnection是相似的,都是可以在不同進程之間進行連接的,不同的是,這里第三種是在對象的當前線程中執行,并且是按照隊列順序執行。當當前線程停止,就會等待下一次啟動線程時再按隊列順序執行 ,等待QApplication::exec()或者線程的QThread::exec()才執行相應的槽,就是說:當控制權回到接受者所依附線程的事件循環時,槽函數被調用,而且槽函數在接收者所依附線程執行,使用這種連接,槽會在線程執行。
第四種Qt::BlockingQueuedConnection是(必須信號和曹在不同線程中,否則直接產生死鎖)這個是完全同步隊列只有槽線程執行完才會返回,否則發送線程也會等待,相當于是不同的線程可以同步起來執行。
第五種Qt::UniqueConnection跟默認工作方式相同,只是不能重復連接相同的信號和槽;因為如果重復鏈接就會導致一個信號發出,對應槽函數就會執行多次。
第六種Qt::AutoCompatConnection是為了連接QT4 到QT3的信號槽機制兼容方式,工作方式跟Qt::AutoConnection一樣。顯然這里我們應該選擇第三種方式,我們不希望子線程沒結束主線程還要等,我們只是希望利用這個空閑時間去干別的事情,當子線程執行完了,只要發消息給主線程就行了,到時候主線程會去響應。
后記
為什么要使用moveToTread()呢。
eg:moveToThread對比傳統子類化Qthread更靈活,僅需要把你想要執行的代碼放到槽,movetothread這個object到線程,然后拿一個信號連接到這個槽就可以讓這個槽函數在線程里執行。可以說,movetothread給我們編寫代碼提供了新的思路,當然不是說子類化qthread不好,只是你應該知道還有這種方式去調用線程。
老大認為,輕量級的函數可以用movethread,多個短小精悍能返回快速的線程函數適用 ,無需創建獨立線程類,例如你有20個小函數要在線程內做, 全部扔給一個QThread。而我覺得movetothread和子類化QThread的區別不大,更可能是使用習慣引導。又或者你一開始沒使用線程,但是后邊發覺這些代碼還是放線程比較好,如果用子類化QThread的方法重新設計代碼,將會有可能讓你把這一段推到重來,這個時候,moveThread的好處就來了,你可以把這段代碼的從屬著movetothread,把代碼移到槽函數,用信號觸發它就行了。其它的話movetothread它的效果和子類化QThread的效果是一樣的,槽就相當于你的run()函數,你往run()里塞什么代碼,就可以往槽里塞什么代碼,子類化QThread的線程只可以有一個入口就是run(),而movetothread就有很多觸發的入口。