今天在Qt编程时,将QTimer在子线程里执行start()函数,遇到“Timers cannot be started from another thread”问题,使用了如下AI工具,进行查询:
提示词A:“C++ QTimer 如何跨线程”
提示词B:“C++ QTimer QThread::run 执行”
提示词C:“C++ QThread::run 在start()之后 会自动退出吗”
问题原因:QTimer本身不支持跨线程调用
解决方法:QTimer的运行,需要它所在的线程支持事件循环,若没有事件循环,则调用QTimer::start()语句时,会报"Timers cannot be started from another thread"错误,即QTimer失效。
所以,要想让QTimer在子线程里正常运行,则需要把该子线程的事件循环开启即可。
注意:
- 若QThread::run()没有被重写override,则默认是开启了事件循环。
- Qt的主线程,也默认开启了事件循环;
- C++ std::thread默认没有事件循环;
1 秘塔AI的回答
秘塔AI官网: https://metaso.cn/
1.1 在QThread::run()函数里执行QTimer
在子线程中创建和使用QTimer:确保QTimer对象在子线程中创建,并且其信号和槽函数也在子线程中处理。
// 代码: codeA
void WorkerThread::run()
{
QTimer timer;
connect(&timer, &QTimer::timeout, this, &WorkerThread::onTimerTick);
timer.start(1000); // 每秒触发一次 timeout 信号
exec(); // 启动事件循环
}
codeA的含义是,在QThread::run()里创建一个QTimer对象,然后,也在run()里执行这个QTimer,
并通过exec()启动事件循环。
1.2 将QTimer对象移动到子线程
创建一个继承自QObject的类,比如Worker类,并在其中创建QTimer对象,然后将该对象移动到子线程中执行。例如:
// 代码: codeB
class Worker : public QObject
{
Q_OBJECT
private slots:
void onTimeout()
{
qDebug() << "Worker::onTimeout get called from?: " << QThread::currentThreadId();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
QThread t;
QTimer timer;
Worker worker;
QObject::connect(&timer, &QTimer::timeout, &worker, &Worker::onTimeout);
timer.start(1000);
worker.moveToThread(&t);
t.start();
return a.exec();
}
1.3 米塔AI的总结
codeA和codeB,都是把QTimer的执行环境,放到子线程里,
- 方法A: 通过在QThread::run()里,新建QTimer对象,让QTimer的执行环境处于子线程中,并在子线程里启动事件循环;
- 方法B: 通过moveThread()函数,把QTimer的执行环境,从主线程移动到子线程;
然后,再调用QTimer::start()函数,即可运行定时器,从而解决"Timers cannot be started from another thread"问题
2 DeepSeek的回答
DeepSeek官网: https://chat.deepseek.com/
其他版本的DeepSeek,比如超算中心DeepSeek网站: https://chat.scnet.cn/#/home
2.1 在QThread::run()函数里执行QTimer
在 C++ 中使用 QTimer 和 QThread 时,如果想在 QThread::run() 中执行 QTimer,则需要注意的是QTimer 依赖于事件循环。
- 若QThread::run()被重写override,则需要手动提供事件循环QEventLoop ,即在run()函数里要加上exec()或者loop.exec()语句。
- 若QThread::run()没有被重写override,则不需要手动提供事件循环QEventLoop 。
以下是一个示例,展示如何在 QThread::run() 中执行 QTimer:
#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>
#include <QEventLoop>
class WorkerThread : public QThread
{
Q_OBJECT
protected:
void run() override
{
qDebug() << "WorkerThread started in thread:" << QThread::currentThreadId();
// 创建一个 QTimer
QTimer timer;
connect(&timer, &QTimer::timeout, this, &WorkerThread::onTimeout);
// 启动定时器,每隔 1 秒触发一次
timer.start(1000);
// 创建一个事件循环以保持线程运行
QEventLoop loop;
connect(this, &WorkerThread::finished, &loop, &QEventLoop::quit); // 线程结束时退出事件循环
loop.exec(); // 进入事件循环
qDebug() << "WorkerThread finished.";
}
private slots:
void onTimeout()
{
qDebug() << "Timeout occurred in thread:" << QThread::currentThreadId();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerThread workerThread;
qDebug() << "Main thread:" << QThread::currentThreadId();
// 启动工作线程
workerThread.start();
// 5 秒后停止工作线程
QTimer::singleShot(5000, [&workerThread]() {
qDebug() << "Stopping worker thread...";
workerThread.quit(); // 退出线程的事件循环
workerThread.wait(); // 等待线程结束
qDebug() << "Worker thread stopped.";
QCoreApplication::quit(); // 退出主事件循环
});
return a.exec();
}
2.2 将QTimer对象移动到子线程
通常,QTimer 在主线程中创建,但可以通过信号和槽机制在另一个线程中控制它。
- a) 在主线程中创建 QTimer;
- b) 将 QTimer通过moveThread()函数,移动到目标线程(也称子线程);
- c) 使用信号和槽进行跨线程通信;
示例代码如下:
#include <QCoreApplication>
#include <QTimer>
#include <QThread>
#include <QDebug>
class Worker : public QObject
{
Q_OBJECT
public:
Worker(QObject *parent = nullptr) : QObject(parent)
{
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &Worker::onTimeout);
}
void startTimer()
{
m_timer->start(1000); // 1秒触发一次
}
void stopTimer()
{
m_timer->stop();
}
public slots:
void onTimeout()
{
qDebug() << "Timeout occurred in thread:" << QThread::currentThreadId();
}
private:
QTimer *m_timer;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread workerThread;
Worker worker;
// 将 worker 移动到 workerThread
worker.moveToThread(&workerThread);
// 启动线程
workerThread.start();
// 使用信号和槽启动定时器
QTimer::singleShot(0, &worker, &Worker::startTimer);
// 5秒后停止定时器并退出线程
QTimer::singleShot(5000, [&]() {
worker.stopTimer();
workerThread.quit();
workerThread.wait();
qDebug() << "Timer stopped, thread finished.";
a.quit();
});
return a.exec();
}
关键点
- moveToThread: 将 Worker 对象移动到新线程,确保 QTimer 在新线程的事件循环中运行。
- 信号和槽: 使用 QTimer::singleShot 和信号槽机制跨线程控制 QTimer。
- 线程安全: QTimer 的操作是线程安全的,前提是它在其所在线程的事件循环中运行。
- 通过 moveToThread 和信号槽机制,可以安全地在多线程环境中使用 QTimer,确保定时器在正确的线程中运行。
3 豆包AI的回答
豆包AI,既有客户端版本,也有网页版本,这里给出的是网页版本的豆包AI地址:
https://www.doubao.com/chat/?channel=browser_landing_page
3.1 在QThread::run()函数里执行QTimer
在Thread::run()函数里,新建一个QTimer对象,然后,调用QTimer::start(),再启动事件循环exec()。
#include <QCoreApplication>
#include <QTimer>
#include <QThread>
#include <QDebug>
// 自定义线程类
class MyThread : public QThread {
Q_OBJECT
public:
explicit MyThread(QObject *parent = nullptr) : QThread(parent) {}
protected:
void run() override {
QTimer timer;
timer.setInterval(1000); // 设置定时器间隔为1秒
connect(&timer, &QTimer::timeout, []() {
qDebug() << "Timer timeout in thread:" << QThread::currentThreadId();
});
timer.start();
exec(); // 启动线程的事件循环
}
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
MyThread thread;
thread.start();
return a.exec();
}
#include "main.moc"
3.2 将业务对象(非QTimer)通过moveThread()移动到子线程,
豆包AI的回答,比前面2个AI: 秘塔AI、DeepSeek的回答,更加的简洁。
它是将纯业务对象Worker移动到子线程,而QTimer仍在主线程,QTimer通过信号槽机制,与业务对象Worker关联起来。
- 将线程QThread的启动信号start,绑定到QTimer的start()槽函数;
- 同时,QTimer的超时信号timeout,绑定Worker的handleTimeout()槽函数;
这种方式,间接的实现了跨线程调用QTimer。
即子线程thread --> 主线程的timer;
#include <QCoreApplication>
#include <QTimer>
#include <QThread>
#include <QDebug>
// 自定义工作类
class Worker : public QObject {
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr) : QObject(parent) {}
public slots:
void handleTimeout() {
qDebug() << "Timer timeout in thread:" << QThread::currentThreadId();
}
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 创建工作对象和线程
Worker worker;
QThread thread;
worker.moveToThread(&thread);
// 创建定时器
QTimer timer;
timer.setInterval(1000); // 设置定时器间隔为1秒
// 连接信号和槽
QObject::connect(&timer, &QTimer::timeout, &worker, &Worker::handleTimeout);
QObject::connect(&thread, &QThread::started, &timer, QOverload<>::of(&QTimer::start));
// 启动线程
thread.start();
return a.exec();
}
4 QTimer跨线程调用的总结
方式 | 直接调用QTimer | 间隔调用QTimer |
---|---|---|
A | 在QThread::run()函数里创建QTimer,并运行QTimer,启动事件循环 | 将QTimer对象移动到子线程 |
B | 在QThread::run()函数里创建QTimer,并运行QTimer,启动事件循环 | 将业务对象Woker移动到子线程,然后将QTimer的超时信号与Worker里的槽函数进行绑定 |