QObject
是Qt框架的基本类,但凡涉及到信号槽有关的类都是继承于QObject
。QObject
是一个功能异常强大的类,它提供了Qt关键技术信号和槽的支持以及事件系统的支持,同时它提供了线程操作的接口,也就是QObject
是可以选择不同的线程里执行的。
QObject
的线程转移函数是:void moveToThread(QThread * targetThread)
,通过此函数可以把一个顶层Object
(就是没有父级)转移到一个新的线程里。
QThread
非常容易被新手误用,主要是QThread
自身并不生存在它run
函数所在的线程,而是生存在旧的线程中。由于QThread
的这个特性,导致在调用QThread
的非run
函数容易在旧线程中执行。
继承QObject,调用moveToThread方法用的时候要强调的几个重点:
自定义的MyThread线程类的对象在创建时不能指定父对象!
启动子线程后,并没有启动子线程处理函数;
启动子线程处理函数必须用signal-slot方式!!!
在线程处理函数内部,绝对不允许操作ui图形界面(比如跳个弹窗等等),线程内部通常是纯数据处理!
1.首先,在工程中新建一个MyThread类,继承于QObject
:
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QObject>
class MyThread : public QObject
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr);
//线程处理函数
void myTimeout();
void setFlag(bool flag=true);
void dealSignal_MyThread();
signals:
void mySignal();
private:
bool isStop;
};
#endif // MYTHREAD_H
mythread.cpp
#include "mythread.h"
#include<QThread>
#include<QDebug>
MyThread::MyThread(QObject *parent) : QObject(parent)
{
isStop=false;//初始化
}
void MyThread::setFlag(bool flag)
{
isStop=flag;
}
void MyThread::myTimeout()
{
while(isStop==false)
{
QThread::sleep(1);
emit mySignal();
qDebug()<<"子线程号:"<<QThread::currentThread();
}
}
void MyThread::dealSignal_MyThread()
{
static int i=0;
i++;
qDebug()<<i;
qDebug()<<"子线程号:"<<QThread::currentThread();
}
2.再来看MyWidget类
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include"mythread.h"
#include<QThread>
QT_BEGIN_NAMESPACE
namespace Ui { class MyWidget; }
QT_END_NAMESPACE
class MyWidget : public QWidget
{
Q_OBJECT
signals:
void startThread();//启动子线程处理函数的信号
public:
MyWidget(QWidget *parent = nullptr);
~MyWidget();
private slots:
void on_ButtonStart_clicked();
void dealSignal_MyWidget();
void dealDestroyed();
void on_ButtonStop_clicked();
private:
Ui::MyWidget *ui;
MyThread *my_thread;
QThread *sub_thread;
};
#endif // MYWIDGET_H
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include<QDebug>
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MyWidget)
{
ui->setupUi(this);
//动态分配空间,不能指定父对象
my_thread=new MyThread;
//创建子线程
sub_thread=new QThread(this);
//把自定义的线程加入到子线程中
my_thread->moveToThread(sub_thread);
connect(my_thread,&MyThread::mySignal,this,&MyWidget::dealSignal_MyWidget);
connect(my_thread,&MyThread::mySignal,my_thread,&MyThread::dealSignal_MyThread);
qDebug()<<"主线程号:"<<QThread::currentThread();//Returns a pointer to a QThread which manages the currently executing thread.
connect(this,&MyWidget::startThread,my_thread,&MyThread::myTimeout);//只能通过signal-slot方式启动子线程处理函数
connect(this,&MyWidget::destroyed,this,&MyWidget::dealDestroyed);
}
MyWidget::~MyWidget()
{
delete ui;
}
void MyWidget::dealSignal_MyWidget()
{
static int i=0;
i++;
ui->lcdNumber->display(i);
//qDebug()<<QThread::currentThread();
}
void MyWidget::on_ButtonStart_clicked()
{
if(sub_thread->isRunning()==true)return;
//启动子线程,但是并没有启动子线程处理函数
sub_thread->start();
my_thread->setFlag(false);
//特别注意不能直接调用子线程处理函数
//直接调用导致子线程处理函数和主线程是在同一个线程
//不能这样写:my_thread->myTimeout();
//只能通过signal-slot方式调用
emit startThread();
}
void MyWidget::on_ButtonStop_clicked()
{
if(sub_thread->isRunning()==false)return;
my_thread->setFlag(true);
sub_thread->quit();
sub_thread->wait();
}
void MyWidget::dealDestroyed()
{
this->on_ButtonStop_clicked();
delete my_thread;//因为没有指定父对象,所以需要手动释放内存
}
程序中的逻辑关系如下图:
程序中几个小细节:
通常关闭子线程时会采用:
sub_thread->quit();
//告诉线程的事件循环以return 0(成功)退出。 相当于调用QThread :: exit(0)。如果线程没有事件循环,这个函数什么也不做。
sub_thread->wait();
//阻塞线程,直到满足以下任一条件:
//与此QThread对象关联的线程已经完成执行(即从run()返回)。 如果线程完成,该函数将返回true。 如果线程尚未启动,它也返回true。
//时间毫秒已经过去了。 如果时间是ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。 如果等待超时,此函数将返回false。
但是如果子线程是个死循环,那么wait是不会起作用的,所以解决办法是设置flag,在关闭子进程时跳出死循环。
面试题:connect()第5个参数的作用?
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
connect()第5个参数的作用是连接方式:默认,队列、直接
多线程时才有意义;
队列(Qt::QueuedConnection):槽函数所在的线程和信号接收者所在的线程一样;
直接(Qt::DirectConnection):槽函数所在的线程和信号发送者所在的线程一样;
默认(Qt::AutoConnection):如果是多线程,默认使用队列;如果是单线程,默认使用直接;
所以,上面程序中:
connect(this,&MyWidget::startThread,my_thread,&MyThread::myTimeout);
等价于:
connect(this,&MyWidget::startThread,my_thread,&MyThread::myTimeout,Qt::QueuedConnection);