目录
1.现象
目前项目上需要使用定时器,首先想到的是用QT自带的定时器QTimer;项目实现如下:
首先在头文件中定义一个QTimer,代码如下:
#pragma once
#include <QTimer>
class CTTNTAppUpCmdPro : public IShortWaveAppUpCmdPro
{
public:
explicit CTTNTAppUpCmdPro(quint64 chaissID, quint64 signalID, void* p, IUploadDataBase* pInterface);
virtual ~CTTNTAppUpCmdPro();
private:
void _dealControlDeviceResponse(const void* p, int source);
private:
void startPollDataTimer();
void endPollDataTimer();
private:
QTimer m_pollDataTimer;
};
接下来实现QTimer的信号timeOut和定时事件的关联,启动定时器,代码如下:
void CTTNTAppUpCmdPro::startPollDataTimer()
{
m_pollDataTimer.setInterval(1000); //1秒5次
QObject::connect(&m_pollDataTimer, &QTimer::timeout, [&]() {
sendQueryDataCmd();
});
m_pollDataTimer.start();
}
void CTTNTAppUpCmdPro::endPollDataTimer()
{
m_pollDataTimer.stop();
}
//设备启动停止应答
void CTTNTAppUpCmdPro::_dealControlDeviceResponse(const void* p, int source)
{
const stShortWavePacket* pAppPacket = static_cast<const stShortWavePacket*>(p);
assert(pAppPacket);
const stLinkDeviceCmdResponse* pCommData = static_cast<const stLinkDeviceCmdResponse*>(pAppPacket->pAppData);
assert(pCommData);
//...
if (pCommData->cmdType == 1 && st.IsSuccess) { //启动成功
startPollDataTimer(); //启动定时查询TTNT数据线程
}
if (pCommData->cmdType == 2 /*&& st.IsSuccess*/) { //停止成功
endPollDataTimer(); //关闭定时查询TTNT数据线程
}
}
经过调试发现,在调用_dealControlDeviceResponse函数的启动定时器startPollDataTimer的时候,在控制台上就会显示:
QObject::startTimer: Timers can only be used with threads started with QThread
2.原因分析
翻开QTimer的源码实现(5.12.12版本)可以看到:
出现QObject::startTimer: Timers can only be used with threads started with QThread的原因是当前对象所属的线程对象的eventDisptcher是nullptr,为什么会出现这样的情况呢?那是因为当前线程不是从QThread派生出来的,QObject::startTimer
方法依赖于Qt的事件循环(event loop)来触发定时器事件。每个QObject对象都与一个特定的线程相关联,这个线程必须是Qt创建并管理的线程。如果你在一个非Qt线程中使用startTimer
,定时器将无法在Qt的事件循环中注册,因此它不会触发任何事件。
3.解决方法
3.1.在QT主线程(GUI线程)中使用QTimer
因为主线程默认是由 Qt 框架管理的,并且是基于 QThread
的。但是,如果你在主线程中创建了一个新的线程,并且在这个新线程中直接使用了 QObject::startTimer
而没有正确地管理这个线程(例如,没有将其作为一个 QThread
对象来启动),就可能会遇到这个错误。
通过调试第1章节的代码发现QTimer的定义和它的startTimer不是在同一个线程中,QTimer的定义既非在主线程中也非在基于QThread的线程中,所以会出现报错;但同时发现startTimer的线程是在主线程中的,所以稍加修改:
#pragma once
#include <QTimer>
#include <memory>
class CTTNTAppUpCmdPro : public IShortWaveAppUpCmdPro
{
public:
explicit CTTNTAppUpCmdPro(quint64 chaissID, quint64 signalID, void* p, IUploadDataBase* pInterface);
virtual ~CTTNTAppUpCmdPro();
private:
void _dealControlDeviceResponse(const void* p, int source);
private:
void startPollDataTimer();
void endPollDataTimer();
private:
std::unique_ptr<QTimer> m_pollDataTimer;
};
void CTTNTAppUpCmdPro::startPollDataTimer()
{
m_pollDataTimer.reset(new QTimer(null))
m_pollDataTimer->setInterval(1000); //1秒5次
QObject::connect(m_pollDataTimer.get(), &QTimer::timeout, [&]() {
sendQueryDataCmd();
});
m_pollDataTimer->start();
}
void CTTNTAppUpCmdPro::endPollDataTimer()
{
m_pollDataTimer->stop();
}
编译运行,果然就能正常触发定时任务了。
3.2.在非GUI线程中使用QTimer
如果你创建了一个继承自 QThread 的类,并在这个类的 run() 方法中直接使用 QObject::startTimer,这通常是可以的,前提是确保这个 QThread 对象已经被正确地启动(即调用了 start() 方法)。然而,如果你在一个普通的 C++ 线程(例如使用 std::thread)中尝试使用 QObject::startTimer,就会触发这个错误,因为 QObject 的定时器机制依赖于 Qt 的事件循环,而 std::thread 并不提供这样的事件循环。
示例代码如下:
#include <QThread>
#include <QTimer>
#include <QObject>
class Worker : public QObject {
Q_OBJECT
public:
void doWork() {
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &Worker::onTimeout);
timer->start(1000); // 每秒触发一次
}
private slots:
void onTimeout() {
// 处理定时器事件
qDebug() << "Timer triggered";
}
};
class Thread : public QThread {
protected:
void run() override {
Worker worker;
worker.doWork(); // 在这个线程中启动定时器
exec(); // 启动Qt的事件循环
}
};
4.总结
- 确保你在一个由
QThread
启动的线程中使用QObject::startTimer
。 - 如果你需要在非GUI线程中使用定时器,确保这个线程是通过继承
QThread
并重写run()
方法来创建的,并且在启动线程时调用了start()
方法。 - 避免在不是由
QThread
管理的线程中使用 Qt 的信号和槽机制或任何依赖于事件循环的功能。