需求:在子线程中使用QTimer定时器定时执行某项任务
正确用法:主要原理,QT对象的线程依附性 “thread affinity”
使用 moveToThread 的方式创建子线程,见下文:
头文件:Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QThread>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QThread m_thread;
};
#endif // WIDGET_H
实现文件:Widget.cpp
#include "Widget.h"
#include "ui_Widget.h"
#include "TimerThread.h"
#include <QDebug>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
qDebug() << "UI thread id:" << QThread::currentThreadId();
TimerThread* timerInSubThread = new TimerThread();
timerInSubThread->moveToThread(&m_thread); //使用moveToThread创建子线程的
connect(&m_thread, SIGNAL(started()), timerInSubThread, SLOT(onCreateTimer())); //在线程启动的时候创建定时器
connect(&m_thread, &QThread::finished, timerInSubThread, &QObject::deleteLater);
m_thread.start();
}
Widget::~Widget()
{
m_thread.quit();
m_thread.wait();
delete ui;
}
子线程的类:
头文件:TimerThread.h
#ifndef TIMERTHREAD_H
#define TIMERTHREAD_H
#include <QObject>
#include <QTimer>
class TimerThread : public QObject
{
Q_OBJECT
public:
explicit TimerThread(QObject *parent = nullptr);
~TimerThread();
public slots:
void onCreateTimer();
void onTimeout();
private:
QTimer* timer = nullptr;
};
#endif // TIMERTHREAD_H
实现文件:TimerThread.cpp
#include "TimerThread.h"
#include <QDebug>
#include <QThread>
TimerThread::TimerThread(QObject *parent) : QObject(parent)
{
}
TimerThread::~TimerThread()
{
timer->stop();
timer->deleteLater();
}
void TimerThread::onCreateTimer()
{
//关键点:在子线程中创建QTimer的对象
timer = new QTimer();
timer->setInterval(1000);
connect(timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
timer->start();
}
void TimerThread::onTimeout()
{
qDebug() << " work thread id:" << QThread::currentThreadId(); //打印出线程ID,看看是否UI线程的ID不同
}
运行结果:
如图,可看出UI线程的ID和定时器所在的线程ID不同,达到预期。
错误用法:
若是将定时器对象的创建放在子线程 TimerThread 类的构造函数,结果会是怎样呢?
见下图:
由输出可以看出,定时器不起效果,提示
“QObject::startTimer: Timers cannot be started from another thread ”
——定时器不能在别的线程启动。
我们来分析下:
刚开始只有一个主线程,TimerThread 的实例是在主线程中创建的
TimerThread* timerInSubThread = new TimerThread();
因为定时器是在 TimerThread 的构造函数中
TimerThread::TimerThread(QObject *parent) : QObject(parent) { timer = new QTimer(); }
所以定时器对象也是在主线程中创建的。然后创建子线程,并在子线程中start()定时器,导致对象的创建和start不在同一个线程中,故报错。
具体的原理可参考官方文档——QT对象线程依附性
每个QObject实例都有一个叫做“线程关系”(thread affinity)的属性,或者说,它处于某个线程中。
默认情况下,QObject处于创建它的线程中。
当QObject接收队列信号(queued signal)或者传来的事件(posted event),槽函数或事件处理器将在对象所处的线程中执行。
延伸:
Qt的所有IO类和QTimer的对象,都不推荐或者不可以跨线程操作,比如说QTcpSocket、QFile、
QUdpSocket等IO类。可以用信号和槽封装一下,或者使用bool QMetaObject::invokeMethod(...)调用