界面编程之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

 

posted @ 2018-07-29 11:18  yuweifeng  阅读(3254)  评论(0编辑  收藏  举报