1、定时器是在当前线程中执行的,实际上,它处于当前线程的事件循环中,如果想定时器放在单独的线程中执行,可以借助于moveToThread实现。
你可以把QTimer看作是一个能够产生定时事件的对象。当你启动一个QTimer时,你实际上是在告诉事件循环:“请在指定的时间后向我发送一个定时器超时事件(Timer Timeout Event)”。当事件循环检测到这个时间已经到达,它就会创建一个定时器超时事件,并将其分发给QTimer对象。然后QTimer对象就会发出一个超时信号(timeout signal),这个信号可以被其他对象捕获并作出相应的响应。
深入理解Qt定时器:QTimer的魅力与挑战(一)-阿里云开发者社区 (aliyun.com)
2、虽然是在当前线程中执行的,但即便把定时器的执行频率设置的非常高,也不会导致当前界面无响应,除非在定时器的处理函数中持续占CPU(例如sleep循环)。
3、假如弄了多个定时器,这些定时器实际上是串行执行的,同一时刻只有一个定时器处理函数在执行。
4、一个主线程和子线程使用各自定时器的例子,如此,两个定时器就可以并发,而且子线程中调用MainWindow的函数即使阻塞也不会影响主线程MainWindow的其它执行。
#ifndef THREADTIMER_H
#define THREADTIMER_H
#include <QObject>
class QThread;
class QTimer;
class MainWindow;
class ThreadTimer : public QObject
{
Q_OBJECT
public:
explicit ThreadTimer(QObject *parent = nullptr);
~ThreadTimer();
private:
void OnTimeOut();
public slots:
void StartTimer(int timeout);
void StopTimer( );
private:
QTimer *m_pTimer;
QThread *m_pCurrThread;
MainWindow *m_pMainWindow;
};
#endif // THREADTIMER_H
#include "threadtimer.h"
#include <QThread>
#include <QTimerEvent>
#include <QDebug>
#include <QTimer>
#include <QTime>
#include "mainwindow.h"
ThreadTimer::ThreadTimer(QObject *parent) : QObject(parent)
{
m_pMainWindow = qobject_cast<MainWindow*>(parent);
m_pCurrThread = new QThread;
m_pTimer = new QTimer(m_pCurrThread);// 不能传this,否则报QObject::startTimer: Timers can only be used with threads started with QThread
connect(m_pTimer, &QTimer::timeout, this, &ThreadTimer::OnTimeOut);
moveToThread(m_pCurrThread);
m_pCurrThread->start();
}
ThreadTimer::~ThreadTimer()
{
if (nullptr != m_pCurrThread)
{
m_pCurrThread->quit();
m_pCurrThread->wait();
m_pCurrThread->deleteLater();
}
}
void ThreadTimer::StartTimer(int timeout)
{
m_pTimer->start(timeout * 1000);
}
void ThreadTimer::StopTimer()
{
m_pTimer->stop();
}
void ThreadTimer::OnTimeOut()
{
qDebug() << "ThreadTimer::OnTimeOut triggered:" << QTime::currentTime().toString("HH::mm::ss") << " threadId:" << QThread::currentThreadId();
m_pMainWindow->testBlock();
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTimer>
#include <QDebug>
#include <QTime>
#include <QThread>
#include "threadtimer.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
testTimer();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::timer1Out()
{
qDebug() << "timer1 triggered:" << QTime::currentTime().toString("HH::mm::ss") << " threadId:" << QThread::currentThreadId();
for (int i = 0; i < 5; i++)
{
qDebug() << "timer1 sleep" << QTime::currentTime().toString("HH::mm::ss") << " threadId:" << QThread::currentThreadId();
QThread::sleep(1);
}
}
void MainWindow::testTimer()
{
QTimer *timer1 = new QTimer(this);
connect(timer1, &QTimer::timeout, this, &MainWindow::timer1Out);
QMenuBar *menubar = menuBar();
setMenuBar(menubar);
QMenu * menuTimer = new QMenu("定时器");
menubar->addMenu(menuTimer);
QAction *action1 = new QAction("开始定时器");
QAction *action2 = new QAction("停止定时器");
menuTimer->addAction(action1);
menuTimer->addAction(action2);
connect(action1, &QAction::triggered, this, [=](){
timer1->start(1000);
ThreadTimer *threadTimer = new ThreadTimer();
threadTimer->StartTimer(1);
});
connect(action2, &QAction::triggered, this, [=](){
timer1->stop();
});
}
void MainWindow::testBlock()
{
qDebug() << "MainWindow::testBlock:" << QTime::currentTime().toString("HH::mm::ss") << " threadId:" << QThread::currentThreadId();
for (int i = 0; i < 5; i++)
{
qDebug() << "testBlock sleep" << QTime::currentTime().toString("HH::mm::ss") << " threadId:" << QThread::currentThreadId();
QThread::sleep(1);
}
}
这篇文章说的大致意思是,QThread对象和子线程运行空间不是一回事,QThread对象本身并不属于子线程空间,因此如果QThread对象是在主线程创建的,那在QThread子类中创建QTimer时传入this,那实际上这个QTimer还是属于QThread对象从而还是属于主线程,它并不能实现在子线程中实现定时器,所以文章说了两种形式:1.在QThread子类中创建QTimer时不传this,结果就是定时器实际上还是运行在主线程中。2.在在QThread子类中创建QTimer时传this,并且,QThread子类对象把自己关联到自己所指示的线程上,即,t1.moveToThread(&t1)。
6、定时器的处理函数不会阻塞定时器的计时,也就是说,定时器计时一直是按正常时间流逝进行的,或者说是准的,但是,假如定时器处理函数处理的时长超过了定时周期,那么这第二次的到期处理函数就不会被调用,那么它就继续下一个周期,也就是2倍周期到期的时候调用,那假如2倍的时候这第一次还没完事呢,那就继续第3个周期的时候........。
7、调用setInterval改变周期会立即生效,比如说,原来是12秒一次,它正计时到3秒呢,你把周期改为5秒一次了,那再过5-3=2秒,它就会立即触发。
8、如果调用了singleShot,之后还没到触发时间又调用了singleShot,那么这两次调用随后都会触发,而且各自按各自的倒计时。为了防止这种情况,有一种替代方案是不用singleShot触发,但是用setSingleShot将定时器设置为单次触发,用法示例:
QTimer *timerSingle = new QTimer(this);
connect(timerSingle, &QTimer::timeout, this, &MainWindow::timerSingleOut);
timerSingle->setSingleShot(true);
。。。。
connect(actionSingle, &QAction::triggered, this, [=](){
timerSingle->start(10*1000);
qDebug() << QTime::currentTime().toString("HH::mm::ss") << ": 开始计时timerSingle";
});
这样即使连续多次执行了start,定时器也只会被触发一次,不过需要注意的是,它被触发的是时刻是从最后那一次start计时的,实际上就相当于前面执行的start都被忽略了。