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