QT線程


一、QObject子類

說明:以串口線程傳輸文件為例子,使用的是MoveTothread函數。

void QObject::moveToThread(QThread *targetThread)可以將QObject里面的所有事件都會被放在targetThread線程中執行。

如果希望某個對象在線程中做某事,那么這個對象也應該在此線程中創建,如果在主線程中創建將不能在子線程中執行。

所以如果想在子線程中操作串口對象進行文件的讀寫,也需要在子線程中創建串口對象。

1、新建類繼承QObject

右鍵項目添加新文件,選擇C++類,選擇基類為QObject,設置類名為Serial

2、在Serial頭文件中聲明串口對象

QSerialPort* seriaport;

3、在Serial中聲明並實現打開串口函數、發送文件函數、接收文件函數、關閉串口函數這些槽函數

4、在主線程頭文件中聲明此類和一個子線程

Serial* serial;

QThread *serialThread ;

5、在主線程構造函數中new出對象

this->serial = new Serial();

this->serialThread = new QThread();

6、在主線程構造函數中將類放進線程里

serial->moveToThread(serialThread);

serialThread.start();

7、在主線程中聲明打開串口、發送文件、接收文件、關閉串口這些信號

8、在主線程中發送對應信號,觸發Serial里的槽函數,這些槽函數將在子線程中執行。

 

 

 

查看當前線程ID:

 

qDebug()<<"id = "<<QThread::currentThreadId();

 二、QThread子類

1、新建類MyThread繼承QThread,重寫run()函數

Q_OBJECT//頭文件中加了這個宏才能使用信號與槽

void run();//.cpp中實現

  while(bool flag){}

}

2、在MainWindow中定義MyThread對象

#include "MyThread.h"

MyThread* myThread = new MyThread();

3、啟動線程

myThread->start();

4、停止線程

myThread->quit();

三、兩種方式結合

結合兩種方式,我有以下理解:

第一種方式適用於有信號發出,直接調用對應QObject中的槽函數即可,每個槽函數適用於單獨做一件事情比如串口的發送數據(而不適合做一個循環)。

如果有多個信號對應這些槽函數,只需要connect起來即可,這些槽函數都會在線程中做,很方便;

第二種適用於等待某個回調事件,比如周立功can通信時的接收數據是沒有信號發出的(畢竟dll不是qt下做的),只有一個讀緩沖區有多少數據的函數,

所以可以在run中死循環觀察緩沖區是否有數據,從而回調接收數據。

看了很多文章,都單獨對兩個方式做了很好的介紹。其實QThread也是有movetothread函數的,說明也可以將QThread子類中的槽函數放在另一個線程中使用,那這樣就可以將兩種方式結合起來。

首先假設主線程id是0x0001,QThread子類myThread本身是個線程的入口(run函數),所以它自己可以看做是個線程,假設id是0x0002,可以將處理函數放在run里面,在主線程中start、quit就可以控制run的開啟和停止;(第二種)

然后在QThread中定義n個槽函數,再在主線程中new一個QThread對象lastThread,假設id是0x0003,然后把myThread.movetothread(lastThread),此時當觸發這n個槽函數時,他們都在0x0003中運行的。(第一種)

這個方式是在使用周立功can api寫can通信時實現的,大致方式如下:

1、新建QThread子類CanThread,重寫run,在run中死循環接收文件;

2、在CanThread中聲明定義一個發送數據槽函數sendCAN();

3、在主線程中開啟CanThread,canThread.start();接收數據;

4、在主線程中new一個QThread對象lastThread,轉移槽函數到此對象,canThread.moveToThread(lastThread);

5、在主線程中需要發送數據時,發出信號控制sendCAN就行了,此時run是在本身線程中執行的,sendCAN是在lastThread中執行的。

 

 

ps:http://blog.csdn.net/life_is_too_hard/article/details/52089723

Qt::AutoConnection: 默認值,使用這個值則連接類型會在信號發送時決定。如果接收者和發送者在同一個線程,則自動使用Qt::DirectConnection類型。如果接收者和發送者不在一個線程,則自動使用Qt::QueuedConnection類型。

Qt::DirectConnection:槽函數會在信號發送的時候直接被調用,槽函數運行於信號發送者所在線程。效果看上去就像是直接在信號發送位置調用了槽函數。這個在多線程環境下比較危險,可能會造成奔潰。

Qt::QueuedConnection:槽函數在控制回到接收者所在線程的事件循環時被調用,槽函數運行於信號接收者所在線程。發送信號之后,槽函數不會立刻被調用,等到接收者的當前函數執行完,進入事件循環之后,槽函數才會被調用。多線程環境下一般用這個。

Qt::BlockingQueuedConnection:槽函數的調用時機與Qt::QueuedConnection一致,不過發送完信號后發送者所在線程會阻塞,直到槽函數運行完。接收者和發送者絕對不能在一個線程,否則程序會死鎖。在多線程間需要同步的場合可能需要這個。

Qt::UniqueConnection:這個flag可以通過按位或(|)與以上四個結合在一起使用。當這個flag設置時,當某個信號和槽已經連接時,再進行重復的連接就會失敗。也就是避免了重復連接。

 

 

PS:

1、在主線程中,做其他類的構造函數,構造函數中的代碼是在主線程中做的。所以QThread子類的構造函數中不要初始化希望在線程中做的對象(如udp、串口對象,不要在QThread的構造函數中new)

2、在QThread中new對象(如udp、串口對象,不要傳this進去,具體原因后面再說)

3、最好在主線程的構造函數里為子線程分配空間和連接信號槽,在主線程的觸發函數里開啟關閉線程。(多次連接信號槽,會導致多次發送此信號並且多次執行槽函數

4、線程高階(總結)

正確退出線程的唯一方式:設置標志位(而不是quit之類的)


QThread::exec()進入事件循環
QThread::quit()、exit(int)停止事件循環[quit()==exit(0)]

QThread只是告訴操作系統一個線程的入口是run,本身還是屬於主線程的對象

除了線程run()里面的代碼,其他都是在依附線程中運行的(主線程中的QThread對象依附主線程)

 

QThread有兩種類型:
1、有消息循環。直接就是QThread實例化的子類。這樣直接實例化QThread對象不會重寫run(),由於run默認調用exec()來進入消息循環
QThread tempThread;
或者
QThread *tempThread = new QThread();
此類線程對象可以用來承載其他對象的槽函數:QObject::moveToThread(&QThread)退出消息循環只需要quit()、exit()。
2、沒有消息循環。想要去除消息循環,定義子類繼承QThread,重寫run(),注意不要在里面加上exec()。
class MyThread: public QThread{
protected:
void run()
{while(flag){do}}
};
此類線程用於循環做某個事情,比如接收數據,退出此線程的方式是flag=false;

高速CAN總線設計方案:(達到1ms)
由於CAN總線數據的傳輸不依賴於某一對象(如串口、TCP/IP都需要特定的對象來作為載體傳輸數據),所以比較特殊,CAN設備的初始化、回收等可以在主線程中完成,只需要在線程中做數據傳輸(串口、TCP/IP對象的初始化、回收也需要在線程里做)
一、在主線程中打開、關閉CAN
二、接收解析
1、為了保證接收數據正確性,自定義QThread子類,在run里while循環接收數據,此線程子類只做數據接收。接收到數據后發送給2中的QObject子類。
2、自定義QObject子類,定義QTimer,20ms(按照情況自定義)做一個定時器槽函數;定義全局變量;然后使用moveToThread將此子類槽函數移動到有消息循環的QThread對象中。
QString allInfo;//總的信息,每解析一個obj就把信息追加到allInfo中
QString rcvInfoNew[20];//每個報文具體的解析數據,這是解析的當前的
QString rcvInfoOld[20];//然后在定時器槽函數中判斷rcvInfoNew[i]==rcvInfoOld[i];如果相等則不做處理,如果不相等則發送給主界面解析框顯示,並把old=new
PS:解析方法應放在自定義方法類中,作為靜態函數使用
3、主界面接收特定信號顯示
三、發送
1、在上面的QObject子類中定義發送槽函數和定時器QTimer,主線程通過調用對象函數將obj和循環發送的間隔時間穿進去。
PVCI_CAN_OBJ obj[100];int objNum=0;
void getSendInfo(PVCI_CAN_OBJ obj,int objNum,int time)//又主線程調用並設置需要發送的obj和間隔時間
{
1、獲取obj;
2、獲取obj個數;
3、獲取間隔時間
4、連接定時器信號槽,當時間到了就執行發送槽函數
5、QTimer::start();
}
void timerOutSlot()//定時器槽函數
{
VCI_Trasmit();
}

PS:退出死循環接收線程,是使標志位為false;退出接收解析和發送,都是調用依附線程的quit();退出消息循環后會自動退出線程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM