/*******************************************************************************************/
一、為什么需要使用線程
圖形界面中一旦使用了線程休眠,圖形界面就不會刷新(不會動),呈現卡住無響應的狀態。
這是由於圖形界面中是單線程的
所以 很復雜的數據處理 耗時長的,就需要創建線程。
QThread 線程類,
qt中的線程睡眠函數:QThread::sleep();
void MyWidget::on_pushButton_clicked()
{
//如果定時器沒有工作
if(myTimer->isActive() == false)
{
myTimer->start(100);
}
//很復雜的數據處理
//需要耗時5s
sleep(5);//圖形界面中一旦使用了線程休眠,圖形界面就不會刷新(不會動),呈現卡住無響應的狀態。
//也就是說由於睡眠,導致前面啟動了的定時器都不工作
myTimer->stop();//過了5s后圖形界面才會有響應。但是
//此時是停了定時器,前面是睡眠 定時器也不工作,所以呈現出一直定時器一直不工作的狀態
}
/*******************************************************************************************/
二、線程
1.Qt4.7以前 線程的使用方法:
1).自定義一個類,繼承於QThread
class MyThread : public QThread
{
public:
void run();//只有這個是線程處理函數(和主線程不在同一個線程),虛函數。
}
2).使用自定義的類創建對象,並開啟線程
MyThread myThread;
//啟動線程
//注意不能直接調用run函數,否則還是在主線程里面運行,而不是在新線程里面
myThread.start();//間接調用run
3).返回處理結果,告訴處理完
可以在類中定義一個信號,然后在需要(處理好后)的時候發送這個信號,來告訴其他線程。
具體見圖《線程1》
2.實現:
在項目中添加一個繼承QThread的類,添加的時候注意由於這個不是控件,所以下拉框選擇QObject,然后
再在文件中把基類改為QThread
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
protected:
//QThread的虛函數
//線程處理函數
//不能直接調用,通過start()間接調用
void run();
signals:
void isDone();
public slots:
};
void MyThread::run()
{
//很復雜的數據處理
//需要耗時5s
sleep(5);
emit isDone();
}
//分配空間
thread = new MyThread(this);
//處理線程中的信號,最好用這種傳統的方式
connect(thread, &MyThread::isDone, this, &MyWidget::dealDone);
//當按窗口右上角x時,窗口觸發destroyed(),否則關了窗口線程還會運行
connect(this, &MyWidget::destroyed, this, &MyWidget::stopThread);
void MyWidget::dealDone()
{
qDebug() << "it is over";
myTimer->stop(); //關閉定時器
}
void MyWidget::on_pushButton_clicked()
{
//如果定時器沒有工作
if(myTimer->isActive() == false)
{
myTimer->start(100);
}
//啟動線程,處理數據
thread->start();
}
void MyWidget::stopThread()
{
//停止線程,不是立馬關閉,釋放線程占用的內存,線程號等資源。和下面配合使用
thread->quit();//類似linux中,pthread_exit()
//等待線程處理完手頭動作
thread->wait();//類似linux中,pthread_join , pthread_detach,
//terminate 強制結束,很暴力,往往會導致內存問題。所以一般不用
}
上述代碼具體見《QThread》

1 #ifndef MYWIDGET_H 2 #define MYWIDGET_H 3 4 #include <QWidget> 5 #include <QTimer> //定時器頭文件 6 #include "mythread.h" //線程頭文件 7 8 namespace Ui { 9 class MyWidget; 10 } 11 12 class MyWidget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit MyWidget(QWidget *parent = 0); 18 ~MyWidget(); 19 20 void dealTimeout(); //定時器槽函數 21 void dealDone(); //線程結束槽函數 22 void stopThread(); //停止線程槽函數 23 24 private slots: 25 void on_pushButton_clicked(); 26 27 private: 28 Ui::MyWidget *ui; 29 30 QTimer *myTimer; //聲明變量 31 MyThread *thread; //線程對象 32 }; 33 34 #endif // MYWIDGET_H

1 #include "mywidget.h" 2 #include "ui_mywidget.h" 3 #include <QThread> 4 #include <QDebug> 5 6 MyWidget::MyWidget(QWidget *parent) : 7 QWidget(parent), 8 ui(new Ui::MyWidget) 9 { 10 ui->setupUi(this); 11 12 myTimer = new QTimer(this); 13 14 //只要定時器啟動,自動觸發timeout() 15 connect(myTimer, &QTimer::timeout, this, &MyWidget::dealTimeout); 16 17 //分配空間 18 thread = new MyThread(this); 19 20 21 connect(thread, &MyThread::isDone, this, &MyWidget::dealDone); 22 23 //當按窗口右上角x時,窗口觸發destroyed() 24 connect(this, &MyWidget::destroyed, this, &MyWidget::stopThread); 25 26 } 27 28 void MyWidget::stopThread() 29 { 30 //停止線程 31 thread->quit(); 32 //等待線程處理完手頭動作 33 thread->wait(); 34 } 35 36 void MyWidget::dealDone() 37 { 38 qDebug() << "it is over"; 39 myTimer->stop(); //關閉定時器 40 } 41 42 void MyWidget::dealTimeout() 43 { 44 static int i = 0; 45 i++; 46 //設置lcd的值 47 ui->lcdNumber->display(i); 48 } 49 50 MyWidget::~MyWidget() 51 { 52 delete ui; 53 } 54 55 void MyWidget::on_pushButton_clicked() 56 { 57 //如果定時器沒有工作 58 if(myTimer->isActive() == false) 59 { 60 myTimer->start(100); 61 } 62 63 //啟動線程,處理數據 64 thread->start(); 65 66 }

1 #ifndef MYTHREAD_H 2 #define MYTHREAD_H 3 4 #include <QThread> 5 6 7 class MyThread : public QThread 8 { 9 Q_OBJECT 10 public: 11 explicit MyThread(QObject *parent = 0); 12 13 protected: 14 //QThread的虛函數 15 //線程處理函數 16 //不能直接調用,通過start()間接調用 17 void run(); 18 19 signals: 20 void isDone(); 21 22 public slots: 23 }; 24 25 #endif // MYTHREAD_H

1 #include "mythread.h" 2 3 MyThread::MyThread(QObject *parent) : QThread(parent) 4 { 5 6 } 7 8 void MyThread::run() 9 { 10 //很復雜的數據處理 11 //需要耗時5s 12 sleep(5); 13 14 emit isDone(); 15 }
3.Qt4.7以后 線程的使用方法:
1).定義:
(1).設定一個類,繼承與QObject
(2).類中設置一個線程函數(只有一個是線程函數,函數名可以任意)
class MyThread : public QObject
{
public:
void fun();//
}
2).使用:
(1).創建自定義線程對象(不能指定父對象),
否則后續就無法加入到QThread線程對象,因為指定后是已經給了窗口了就不能放到線程的地方了
或者說已經有父對象的則不能再移動了(不能移動到別的父對象)
myThread=new MyThread;
(2).創建QThread線程對象
QThread *thread = new QThread(this)
(3).把自定義線程類,加入到QThread線程對象, 關聯起來
myThread->moveToThread(thread);
(4).啟動線程對象的線程,
thread.start();//只是把線程開啟了,並沒有啟動線程處理函數(線程是啟動了,但是線程函數沒有啟動)
(5).線程處理函數(自定義線程函數)的啟動,必須通過signal-slot 信號和槽的方式(不能直接調用這個函數):
主線程發送信號,並且信號的處理函數指定為線程處理函數,這樣線程處理函數才啟動了
(定義信號與槽必須要有Q_OBJECT宏)
connect(this, &MyWidget::startThread, myT, &MyThread::myTimeout);
(6).線程退出,直接退出是不行的,因為線程處理函數中的while(1)這樣退出不了
//當按窗口右上角x時,窗口觸發destroyed(),如果此時不關閉線程,則關了窗口線程還會運行
connect(this, &MyWidget::destroyed, this, &MyWidget::dealClose);//不像linux中進程關了則線程都關了,這里線程還會運行
注意使用前面:
thread->quit();//等待線程處理完,但是線程是死循環一致處理不完,所以線程無法退出
thread->wait();
這樣線程不退出,必須先讓線程處理函數退出循環,通過標志位的方法:
myT->setFlag(true);//設置退出標記,則循環條件不滿足,則線程處理函數會退出
thread->quit();
thread->wait()
3).返回處理結果,
可以在類中定義一個信號,信號可以是帶參數的,參數可以是處理后的數據
然后在需要(處理好后)的時候發送這個信號,來告訴其他線程。
具體見圖《線程2》
4.實現:
void MyThread::myTimeout()
{
while( !isStop )
{
QThread::sleep(1);
emit mySignal();
//QMessageBox::aboutQt(NULL);
qDebug() << "子線程號:" << QThread::currentThread();
if(isStop)
{
break;
}
}
}
void MyWidget::on_buttonStart_clicked()
{
if(thread->isRunning() == true)
{
return;
}
//啟動線程,但是沒有啟動線程處理函數
thread->start();
myT->setFlag(false);
//不能直接調用線程處理函數,
//直接調用,導致,線程處理函數和主線程是在同一個線程
//myT->myTimeout();
//只能通過 signal - slot 方式調用
emit startThread();
}
void MyWidget::on_buttonStop_clicked()
{
if(thread->isRunning() == false)
{
return;
}
myT->setFlag(true);
thread->quit();
thread->wait();
}
5.注意:
1).線程處理函數內部,不允許操作圖形界面,也不允許創建圖形界面,否則程序會奔潰。
線程處理函數內部一般是純數據處理
2).connect()第五個參數的作用,第五個參數多線程時才有意義
connect()第五個參數表示的是連接方式,主要有三種:自動連接,隊列連接,直接連接
默認的時候,用的是自動連接,自動連接時:
如果是多線程,默認使用隊列連接 (通過(接收的)類來判斷是否是子線程的)
如果是單線程, 默認使用直接連接方式
隊列連接含義: 槽函數所在的線程和接收者一樣
connect(this, &MyWidget::startThread, myT, &MyThread::myTimeout);
//槽函數所在的線程和myT一樣,即在子線程中
直接連接含義:槽函數所在線程和發送者一樣
connect(this, &MyWidget::startThread, myT, &MyThread::myTimeout,Qt::DirectConnection);
//槽函數所在的線程和this一樣,即在主線程中,無法實現多任務
這就是為啥,啟動線程處理函數使用connect的原因,默認是隊列方式(多線程下)。
一般用默認就夠了
上述代碼具體見《ThreadPro》

1 #ifndef MYWIDGET_H 2 #define MYWIDGET_H 3 4 #include <QWidget> 5 #include "mythread.h" 6 #include <QThread> 7 8 namespace Ui { 9 class MyWidget; 10 } 11 12 class MyWidget : public QWidget 13 { 14 Q_OBJECT 15 16 public: 17 explicit MyWidget(QWidget *parent = 0); 18 ~MyWidget(); 19 20 void dealSignal(); 21 void dealClose(); 22 23 signals: 24 void startThread(); //啟動子線程的信號 25 26 private slots: 27 void on_buttonStart_clicked(); 28 29 void on_buttonStop_clicked(); 30 31 private: 32 Ui::MyWidget *ui; 33 MyThread *myT; 34 QThread *thread; 35 36 }; 37 38 #endif // MYWIDGET_H

1 #include "mywidget.h" 2 #include "ui_mywidget.h" 3 #include <QDebug> 4 5 6 MyWidget::MyWidget(QWidget *parent) : 7 QWidget(parent), 8 ui(new Ui::MyWidget) 9 { 10 ui->setupUi(this); 11 12 //動態分配空間,不能指定父對象 13 myT = new MyThread; 14 15 //創建子線程 16 thread = new QThread(this); 17 18 //把自定義線程加入到子線程中 19 myT->moveToThread(thread); 20 21 connect(myT, &MyThread::mySignal, this, &MyWidget::dealSignal); 22 23 qDebug() << "主線程號:" << QThread::currentThread(); 24 25 connect(this, &MyWidget::startThread, myT, &MyThread::myTimeout); 26 27 28 connect(this, &MyWidget::destroyed, this, &MyWidget::dealClose); 29 30 //線程處理函數內部,不允許操作圖形界面 31 32 33 //connect()第五個參數的作用,連接方式:默認,隊列,直接 34 //多線程時才有意義 35 //默認的時候 36 //如果是多線程,默認使用隊列 37 //如果是單線程, 默認使用直接方式 38 //隊列: 槽函數所在的線程和接收者一樣 39 //直接:槽函數所在線程和發送者一樣 40 41 42 } 43 44 MyWidget::~MyWidget() 45 { 46 delete ui; 47 } 48 49 void MyWidget::dealClose() 50 { 51 on_buttonStop_clicked(); 52 delete myT; 53 } 54 55 void MyWidget::dealSignal() 56 { 57 static int i = 0; 58 i++; 59 ui->lcdNumber->display(i); 60 } 61 62 void MyWidget::on_buttonStart_clicked() 63 { 64 65 if(thread->isRunning() == true) 66 { 67 return; 68 } 69 70 //啟動線程,但是沒有啟動線程處理函數 71 thread->start(); 72 myT->setFlag(false); 73 74 //不能直接調用線程處理函數, 75 //直接調用,導致,線程處理函數和主線程是在同一個線程 76 //myT->myTimeout(); 77 78 //只能通過 signal - slot 方式調用 79 emit startThread(); 80 81 82 } 83 84 void MyWidget::on_buttonStop_clicked() 85 { 86 if(thread->isRunning() == false) 87 { 88 return; 89 } 90 91 myT->setFlag(true); 92 thread->quit(); 93 thread->wait(); 94 }

1 #ifndef MYTHREAD_H 2 #define MYTHREAD_H 3 4 #include <QObject> 5 6 class MyThread : public QObject 7 { 8 Q_OBJECT 9 public: 10 explicit MyThread(QObject *parent = 0); 11 12 //線程處理函數 13 void myTimeout(); 14 15 void setFlag(bool flag = true); 16 17 signals: 18 void mySignal(); 19 20 public slots: 21 22 private: 23 bool isStop; 24 }; 25 26 #endif // MYTHREAD_H

1 #include "mythread.h" 2 #include <QThread> 3 #include <QDebug> 4 #include <QMessageBox> 5 6 MyThread::MyThread(QObject *parent) : QObject(parent) 7 { 8 isStop = false; 9 } 10 11 void MyThread::myTimeout() 12 { 13 while( !isStop ) 14 { 15 16 QThread::sleep(1); 17 emit mySignal(); 18 //QMessageBox::aboutQt(NULL); 19 20 qDebug() << "子線程號:" << QThread::currentThread(); 21 22 if(isStop) 23 { 24 break; 25 } 26 } 27 } 28 29 void MyThread::setFlag(bool flag) 30 { 31 isStop = flag; 32 }
兩種使用線程的方法,新方式是推薦的,但是老方式更好用。
/*******************************************************************************************/
三、線程畫圖
線程中可以繪圖,可以使用繪圖設備QImage
繪畫完畢后可以把繪圖結果即繪圖設備,通過帶參數(即繪圖設備)的信號發送給主窗口
當繪圖很復雜時,當然是放在線程中最合適。
void MyThread::drawImage()
{
//定義QImage繪圖設備
QImage image(500, 500, QImage::Format_ARGB32);
//定義畫家,指定繪圖設備
QPainter p(&image);
//定義畫筆對象
QPen pen;
pen.setWidth(5); //設置寬度
//把畫筆交給畫家
p.setPen(pen);
//定義畫刷
QBrush brush;
brush.setStyle(Qt::SolidPattern); //設置樣式
brush.setColor(Qt::red); //設置顏色
//把畫刷交給畫家
p.setBrush(brush);
//定義5個點
QPoint a[] =
{
QPoint(qrand()%500, qrand()%500),
QPoint(qrand()%500, qrand()%500),
QPoint(qrand()%500, qrand()%500),
QPoint(qrand()%500, qrand()%500),
QPoint(qrand()%500, qrand()%500)
};
p.drawPolygon(a, 5);
//通過信號發送圖片
emit updateImage(image);
}
上述代碼具體見《ThreadIamge》

1 #ifndef WIDGET_H 2 #define WIDGET_H 3 4 #include <QWidget> 5 #include "mythread.h" 6 #include <QThread> 7 #include <QImage> 8 9 namespace Ui { 10 class Widget; 11 } 12 13 class Widget : public QWidget 14 { 15 Q_OBJECT 16 17 public: 18 explicit Widget(QWidget *parent = 0); 19 ~Widget(); 20 21 //重寫繪圖事件 22 void paintEvent(QPaintEvent *); 23 24 void getImage(QImage); //槽函數 25 void dealClose(); //窗口關閉槽函數 26 27 private: 28 Ui::Widget *ui; 29 QImage image; 30 MyThread *myT; //自定義線程對象 31 QThread *thread; //子線程 32 }; 33 34 #endif // WIDGET_H

1 #include "widget.h" 2 #include "ui_widget.h" 3 #include <QPainter> 4 #include <QThread> 5 6 Widget::Widget(QWidget *parent) : 7 QWidget(parent), 8 ui(new Ui::Widget) 9 { 10 ui->setupUi(this); 11 12 //自定義類對象,分配空間,不可以指定父對象 13 myT = new MyThread; 14 15 //創建子線程 16 thread = new QThread(this); 17 18 //把自定義模塊添加到子線程 19 myT->moveToThread(thread); 20 21 //啟動子線程,但是,並沒有啟動線程處理函數 22 thread->start(); 23 24 //線程處理函數,必須通過signal - slot 調用 25 connect(ui->pushButton, &QPushButton::pressed, myT, &MyThread::drawImage); 26 connect(myT, &MyThread::updateImage, this, &Widget::getImage); 27 28 connect(this, &Widget::destroyed, this, &Widget::dealClose); 29 30 } 31 32 Widget::~Widget() 33 { 34 delete ui; 35 } 36 37 void Widget::dealClose() 38 { 39 //退出子線程 40 thread->quit(); 41 //回收資源 42 thread->wait(); 43 delete myT; 44 45 } 46 47 void Widget::getImage(QImage temp) 48 { 49 image = temp; 50 update(); //更新窗口,間接調用paintEvent() 51 } 52 53 void Widget::paintEvent(QPaintEvent *) 54 { 55 QPainter p(this); //創建畫家,指定繪圖設備為窗口 56 p.drawImage(50, 50, image); 57 }

1 #ifndef MYTHREAD_H 2 #define MYTHREAD_H 3 4 #include <QObject> 5 #include <QImage> 6 7 class MyThread : public QObject 8 { 9 Q_OBJECT 10 public: 11 explicit MyThread(QObject *parent = 0); 12 13 //線程處理函數 14 void drawImage(); 15 16 signals: 17 void updateImage(QImage temp); 18 19 public slots: 20 }; 21 22 #endif // MYTHREAD_H

1 #include "mythread.h" 2 #include <QPainter> 3 #include <QPen> 4 #include <QBrush> 5 #include <QImage> 6 7 MyThread::MyThread(QObject *parent) : QObject(parent) 8 { 9 10 } 11 12 void MyThread::drawImage() 13 { 14 //定義QImage繪圖設備 15 QImage image(500, 500, QImage::Format_ARGB32); 16 //定義畫家,指定繪圖設備 17 QPainter p(&image); 18 19 20 //定義畫筆對象 21 QPen pen; 22 pen.setWidth(5); //設置寬度 23 //把畫筆交給畫家 24 p.setPen(pen); 25 26 //定義畫刷 27 QBrush brush; 28 brush.setStyle(Qt::SolidPattern); //設置樣式 29 brush.setColor(Qt::red); //設置顏色 30 //把畫刷交給畫家 31 p.setBrush(brush); 32 33 //定義5個點 34 QPoint a[] = 35 { 36 QPoint(qrand()%500, qrand()%500), 37 QPoint(qrand()%500, qrand()%500), 38 QPoint(qrand()%500, qrand()%500), 39 QPoint(qrand()%500, qrand()%500), 40 QPoint(qrand()%500, qrand()%500) 41 }; 42 43 p.drawPolygon(a, 5); 44 45 46 //通過信號發送圖片 47 emit updateImage(image); 48 49 }