? ? 今天在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_OBJECTprivate 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_OBJECTprotected:void run() override{qDebug() << "WorkerThread started in thread:" << QThread::currentThreadId();// 創建一個 QTimerQTimer 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_OBJECTpublic: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 移動到 workerThreadworker.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里的槽函數 |