界面編程之QT的線程20180731


/*******************************************************************************************/

一、為什么需要使用線程

圖形界面中一旦使用了線程休眠,圖形界面就不會刷新(不會動),呈現卡住無響應的狀態。

這是由於圖形界面中是單線程的

所以  很復雜的數據處理 耗時長的,就需要創建線程。

 

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
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 }
mywidget.cpp
 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
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 }
mythread.cpp

 

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
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 }
mywidget.cpp
 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
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 }
mythread.cpp

 

兩種使用線程的方法,新方式是推薦的,但是老方式更好用。

/*******************************************************************************************/

三、線程畫圖

線程中可以繪圖,可以使用繪圖設備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
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 }
widget.cpp
 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
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 }
mythread.cpp

 


免責聲明!

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



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