/*******************************************************************************************/
一、为什么需要使用线程
图形界面中一旦使用了线程休眠,图形界面就不会刷新(不会动),呈现卡住无响应的状态。
这是由于图形界面中是单线程的
所以 很复杂的数据处理 耗时长的,就需要创建线程。
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 #include5 #include //定时器头文件 6 #include "mythread.h" //线程头文件 7 8 namespace Ui { 9 class MyWidget;10 }11 12 class MyWidget : public QWidget13 {14 Q_OBJECT15 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 #include4 #include 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 #include5 6 7 class MyThread : public QThread 8 { 9 Q_OBJECT10 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 //需要耗时5s12 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 #include5 #include "mythread.h" 6 #include 7 8 namespace Ui { 9 class MyWidget;10 }11 12 class MyWidget : public QWidget13 {14 Q_OBJECT15 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 #include4 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 #include5 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 #include3 #include 4 #include 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 #include5 #include "mythread.h" 6 #include 7 #include 8 9 namespace Ui {10 class Widget;11 }12 13 class Widget : public QWidget14 {15 Q_OBJECT16 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 #include4 #include 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 #include5 #include 6 7 class MyThread : public QObject 8 { 9 Q_OBJECT10 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 #include3 #include 4 #include 5 #include 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 }