QT的线程
文章目录
- QT的线程
- 1、为什么要用线程?
- 2、QT线程
- 1. QT4线程
- 1. QT5线程![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2a9e78f4a64b47f9b516bb02331e9022.png)
- 3. QT5线程的退出
- 4. connect的第五个参数
- 1. 直接方式
- 2. 队列方式
- 3、QT线程绘制QImage图片然后给到主线程显示刷新。
- 4、多线程使用过程中注意事项
- 5.总结
1、为什么要用线程?
看如下的dome:
ui的界面如下:
mywidget.h
#include <QWidget>
#include <QTimer>QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACEclass MyWidget : public QWidget
{Q_OBJECTpublic:MyWidget(QWidget *parent = nullptr);~MyWidget();QTimer *ptime;
private slots:void on_pushButton_clicked();void TimeDealSlot(void);private:Ui::MyWidget *ui;
};
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>MyWidget::MyWidget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget)
{ui->setupUi(this);ptime = new QTimer(this);connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);}MyWidget::~MyWidget()
{delete ui;
}void MyWidget::TimeDealSlot(void)
{static int i = 0;i++;ui->lcdNumber->display(i);
}void MyWidget::on_pushButton_clicked()
{/* 启动定时器 */ptime->start(1000);/* 这个6s的延时当做是很复杂的数据处理 */QThread::sleep(6);}
按下开始按钮启动定时器然后lcdNumber每隔一秒加1显示。但是后面有6s的延时的操作。其结果并不是我们想着这样。
真实的结果是6s执行完后lcdNumber每隔一秒才加1显示。因为在6s的延时时。都在处理这个延时。其它事情没法处理。
只能一件一件做完。当然如果每件事情都不是很复杂那也不会有影响。但这个复杂的6s延时是会有影响的。所以对于很复杂的
数据处理就需要另外开启一个线程。让这个线程来处理。
线程的优势:
多线程程序有以下几个特点:
2、QT线程
1. QT4线程
- 定义一个类继承与QThread。
- 在类里面重写void run()的虚函数(这个run就是线程处理函数并且run的访问限定符是protected。
- start()启动线程函数run。注意不能直接调用run()函数。
- 线程的退出Thread->quit() pThread->wait();
模拟的流程如下:
mywidget.h
#include <QWidget>
#include <QTimer>
#include "mythread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget
{Q_OBJECT
public:MyWidget(QWidget *parent = nullptr);~MyWidget();QTimer *ptime;Mythread *pThread;
private slots:void on_pushButton_clicked();void TimeDealSlot(void);void WidgetSlot(void);
private:Ui::MyWidget *ui;
};
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>MyWidget::MyWidget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget)
{ui->setupUi(this);ptime = new QTimer(this);pThread = new Mythread(this);/* 定时器溢出信号timeout */connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);/* 当按窗口右上角x时,窗口触发destroyed()信号 */connect(this, &MyWidget::destroyed, this, &MyWidget::WidgetSlot);}
MyWidget::~MyWidget()
{delete ui;
}void MyWidget::TimeDealSlot(void)
{static int i = 0;i++;ui->lcdNumber->display(i);
}
void MyWidget::WidgetSlot(void)
{/* 停止线程 */pThread->quit();/* 等待线程处理完手头动作 */pThread->wait();
}
void MyWidget::on_pushButton_clicked()
{/* 如果定时器没有工作 */if(ptime->isActive() == false) {/* 启动定时器 */ptime->start(1000);}/* 启动线程 不能直接调run函数 */pThread->start();
}
mythread.h
#include <QThread>
class Mythread : public QThread
{Q_OBJECT
public:explicit Mythread(QObject *parent = nullptr);protected://QThread的虚函数//线程处理函数//不能直接调用,通过start()间接调用void run();
signals:};
mythread.cpp
#include "mythread.h"Mythread::Mythread(QObject *parent): QThread{parent}
{}void Mythread::run()
{/* 这个6s的延时当做是很复杂的数据处理 */QThread::sleep(6);
}
当开启一个线程并且把这个延时放到线程里处理。按下开始按钮启动定时器然后lcdNumber每隔一秒加1显示。这样就不会有影响。
1. QT5线程
ui的界面
- 创建一个类继承与QObject
- 在类中定义一个线程处理函数(有且只有一个)
#include <QObject>class mythread : public QObject
{Q_OBJECT
public:explicit mythread(QObject *parent = nullptr);/* 自定义线程处理函数 */void ThreadDeal(void);
};
- 在主线程里定义自定义线程与 子线程。并且自定义线程不能指定父对象。指定了会报一个错误。
QThread *pThread; /* 子线程 */
mythread *pMyThread; /* 自定义线程 */
//动态分配空间,不能指定父对象
pMyThread = new mythread;
//创建子线程
pThread = new QThread(this);
- 把自定义线程加入到子线程。
//把自定义线程加入到子线程中 子线程 与 自定义线程建立联系pMyThread->moveToThread(pThread);
- 启动子线程,但是没有启动线程处理函数
//启动子线程,但是没有启动线程处理函数pThread->start();
- 在启动子线程后发送一个自定义信号。通过信号与槽去启动线程处理函数。直接调用启动是没有用的。
//只能通过 signal - slot 方式调用
emit startThread(); /* 发送信号 */
/* 接收信号去启动线程处理函数 */
connect(this, &MyWidget::startThread, pMyThread, &mythread::myTimeout);
- 线程退出
pThread->quit();pThread->wait();
总体的流程:
mywidget.h
#include <QWidget>
#include <QThread>
#include "mythread.h"
#include <QTimer>QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACEclass MyWidget : public QWidget
{Q_OBJECTpublic:MyWidget(QWidget *parent = nullptr);~MyWidget();QThread *pThread; /* 子线程 */mythread *pMyThread; /* 自定义线程 */QTimer *ptime;private slots:void on_pushButtonStart_clicked();void on_pushButtonStop_clicked();void WidgetSlot(void);void TimeDealSlot(void);signals:void StartThreadDealSignal();private:Ui::MyWidget *ui;
};
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget)
{ui->setupUi(this);ptime = new QTimer(this);/* 自定义线程 不能指定父对象 */pMyThread = new mythread;/* 创建子线程 */pThread = new QThread(this);/* 把自定义线程加入到子线程 */pMyThread->moveToThread(pThread);/* 定时器溢出信号timeout */connect(ptime, &QTimer::timeout, this, &MyWidget::TimeDealSlot);/* 当按窗口右上角x时,窗口触发destroyed()信号 */connect(this, &MyWidget::destroyed, this, &MyWidget::WidgetSlot);/* 信号与槽去调用线程处理函数 */connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal);
}MyWidget::~MyWidget()
{delete ui;
}void MyWidget::TimeDealSlot(void)
{static int i = 0;i++;ui->lcdNumber->display(i);
}
void MyWidget::on_pushButtonStart_clicked()
{/* 线程启动已经开启就退出 */if(pThread->isRunning() == true) {return;}qDebug() << "主线程号:" << QThread::currentThread();/* 如果定时器没有工作 */if(ptime->isActive() == false) {/* 启动定时器 */ptime->start(1000);}/* 启动子线程 但并没有启动线程处理函数 */pThread->start();/* 发送StartThreadDealSignal信号启动线程处理函数 */emit StartThreadDealSignal();
}void MyWidget::WidgetSlot(void)
{on_pushButtonStop_clicked();delete pMyThread;
}void MyWidget::on_pushButtonStop_clicked()
{/* 线程启动已经停止就退出 */if(pThread->isRunning() == false) {return;}/* 停止线程 */pThread->quit();/* 等待线程处理完手头动作 */pThread->wait();
}
mythread.h
#include <QObject>class mythread : public QObject
{Q_OBJECT
public:explicit mythread(QObject *parent = nullptr);/* 自定义线程处理函数 */void ThreadDeal(void);signals:};
mythread.cpp
#include "mythread.h"
#include<QThread>
#include<QDebug>
mythread::mythread(QObject *parent): QObject{parent}
{}void mythread::ThreadDeal(void)
{/* 这个6s的延时当做是很复杂的数据处理 */QThread::sleep(6);qDebug() << "子线程号:" << QThread::currentThread();
}
3. QT5线程的退出
pThread->quit()是会等到线程手头的工作处理完才退出。但如果线程的工作处理不完。比如死循环。那怎么退出呢?
/* 这个6s的延时当做是很复杂的数据处理 */while(1) {QThread::sleep(6);qDebug() << "子线程号:" << QThread::currentThread();}
- 强制退出
用pThread->terminate()代替pThread->quit。但是这样可能会导致内存问题。强制退出风险很大。 - 在自定义类里定义一个标志位
mythread.h
void setThreadQuitFlag(int flag); /* 设置标志位函数 */int ThreadQuitFlag; // 定义一个标志位
mythread.cpp
#include "mythread.h"
#include<QThread>
#include<QDebug>
mythread::mythread(QObject *parent): QObject{parent}
{ThreadQuitFlag = false;
}void mythread::ThreadDeal(void)
{/* 这个6s的延时当做是很复杂的数据处理 */while(1) {QThread::sleep(6);qDebug() << "子线程号:" << QThread::currentThread();if (ThreadQuitFlag == true) {break; // 退出线程}}}
void mythread::setThreadQuitFlag(int flag)
{ThreadQuitFlag = flag;
}
在主线程里一开始启动线程设置标志为
pMyThread->setThreadQuitFlag(false);
在主线程里结束线程设置标志为true就可以退出线程
pMyThread->setThreadQuitFlag(true);
4. connect的第五个参数
只介绍队列与直接方式。多线程时才有意义。
如果是多线程,默认使用队列方式。
如果是单线程, 默认使用直接方式。
队列: 槽函数所在的线程和接收者一样。
直接:槽函数所在线程和发送者一样。
1. 直接方式
在如上QT5线程的dome中设置第五个参数 Qt::DirectConnection
/* 信号与槽去调用线程处理函数 */connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal, Qt::DirectConnection);
槽函数所在线程和发送者(主窗口)一样。
2. 队列方式
在如上QT5线程的dome中设置第五个参数 Qt::DirectConnection
/* 信号与槽去调用线程处理函数 */connect(this, &MyWidget::StartThreadDealSignal, pMyThread, &mythread::ThreadDeal, Qt::QueuedConnection);
队列: 槽函数所在的线程和接收者(子线程)一样。
3、QT线程绘制QImage图片然后给到主线程显示刷新。
具体的流程:
1.点击绘图按钮调用线程处理函数
2. 线程里绘图图片但是不显示。绘制一次刷新一次。发送图片给主线程
3. 主线程拿到子线程的图片。在主窗口刷新显示。
伪代码如下:
注意:QImage才能在线程里绘图。
ui界面如下:就一个按钮
widget.h
#include <QWidget>
#include "mythread.h"
#include <QThread>
#include <QImage>namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();QTimer *pTime;//重写绘图事件void paintEvent(QPaintEvent *);
public slots:void getImage(QImage); //槽函数void dealClose(); //窗口关闭槽函数private:Ui::Widget *ui;QImage image;MyThread *myT; //自定义线程对象QThread *thread; //子线程
};
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
#include <QThread>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);this->resize(600,600);//自定义类对象,分配空间,不可以指定父对象myT = new MyThread;//创建子线程thread = new QThread(this);//把自定义模块添加到子线程myT->moveToThread(thread);//启动子线程,但是,并没有启动线程处理函数thread->start();//线程处理函数,必须通过signal - slot 调用connect(ui->pushButton, &QPushButton::pressed, myT, &MyThread::drawImage);connect(myT, &MyThread::updateImage, this, &Widget::getImage);connect(this, &Widget::destroyed, this, &Widget::dealClose);}Widget::~Widget()
{delete ui;
}void Widget::dealClose()
{//退出子线程thread->quit();//回收资源thread->wait();delete myT;}void Widget::getImage(QImage temp)
{image = temp;update(); //更新窗口,间接调用paintEvent()
}void Widget::paintEvent(QPaintEvent *)
{QPainter p(this); //创建画家,指定绘图设备为窗口p.drawImage(50, 50, image);
}
mythread.h
#include <QObject>
#include <QImage>class MyThread : public QObject
{Q_OBJECT
public:explicit MyThread(QObject *parent = 0);//线程处理函数void drawImage();signals:void updateImage(QImage temp);public slots:
};
mythread.cpp
#include "mythread.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QImage>
#include <QRandomGenerator>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
}
void MyThread::drawImage()
{//定义QImage绘图设备QImage image(500, 500, QImage::Format_ARGB32); // QImage::Format_ARGB32透明的颜色//定义画家,指定绘图设备QPainter p(&image);//定义画笔对象QPen pen;pen.setWidth(5); //设置宽度//把画笔交给画家p.setPen(pen);//定义画刷QBrush brush;brush.setStyle(Qt::SolidPattern); //设置样式brush.setColor(QColor(QRandomGenerator::global()->bounded(0,255),QRandomGenerator::global()->bounded(0,255),QRandomGenerator::global()->bounded(0,255))); //随机设置颜色//把画刷交给画家p.setBrush(brush);//定义点QPoint a[] ={QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500)),QPoint(QRandomGenerator::global()->bounded(0,500), QRandomGenerator::global()->bounded(0,500))};/* 绘制多边形 */p.drawPolygon(a, sizeof(a)/sizeof(a[0]));//通过信号发送图片emit updateImage(image);
}
4、多线程使用过程中注意事项
需要移动到子线程中处理的模块类,创建的对象的时候不能指定父对象。但在关闭窗口时要记得释放。
//自定义类对象,分配空间,不可以指定父对象myT = new MyThread;
线程不能操作UI对象(从Qwidget直接或间接派生的窗口对象)
#include "mythread.h"
#include<QThread>
#include<QDebug>
#include<QMessageBox>
mythread::mythread(QObject *parent): QObject{parent}
{ThreadQuitFlag = false;
}
void mythread::ThreadDeal(void)
{/* 这个6s的延时当做是很复杂的数据处理 */while(1) {QThread::sleep(6);qDebug() << "子线程号:" << QThread::currentThread();if (ThreadQuitFlag == true) {break; // 退出线程}/* 在线程处理函数里操作UI对象 */QMessageBox::about(NULL,"关于","在线程处理函数内部不能操作图形界面");}}
QMessageBox::about(NULL,“关于”,“在线程处理函数内部不能操作图形界面”)
这样会导致程序崩掉。
在线程处理函数内部不能操作图形界面。只能是纯数据处理。
5.总结
主要介绍QT的线程的两种用法。connect的第五个参数 。以及线程退pThread->terminate()与pThread->quit函数不一样。以及用pThread->quit。如果线程退不出来(死循环)用标志位来退出。还有线程里不能操作图形界面。