異步實現事件的定時執行 - QTimer和QThread的聯合使用
- 引言
- 一、核心源碼
- 二、其信號和槽函數簡述
- 三、定時器及其moveToThread簡述
引言
在 Qt 中,如果想要定時執行某些事件或函數,通常會使用 QTimer 類。QTimer 允許設置一個時間間隔,當這個時間間隔過去后,它會發出一個信號。可以將這個信號連接到一個槽函數,從而在該時間間隔到達時執行特定的操作。如果想要實現定時的操作是異步執行 (不阻塞主線程),可通過
moveToThread
將定時器移動到一個線程中,信號和槽的連接類型使用Qt::DirectConnection
,保證槽函數執行是在定時器的線程中。效果如下圖所示 (一秒執行一次):
一、核心源碼
-
- 創建定時器以及線程,設定執行事件
this->m_timer = new QTimer(nullptr);this->m_thread = new QThread(this);m_timer->setInterval(1000);m_timer->moveToThread(m_thread);connect(m_timer, &QTimer::timeout, this, [&](){QString time = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");qDebug()<< time << " timer Thread ID:" << QThread::currentThreadId();}, Qt::DirectConnection);connect(m_thread, &QThread::started, m_timer, QOverload<>::of(&QTimer::start));connect(m_thread, &QThread::finished, m_timer, &QTimer::stop);m_thread->start();qDebug() << "Main Thread ID:" << QThread::currentThreadId();
首先創建定時器和線程,設定定時器間隔時間為1秒,并將定時器移動到線程中。方便起見直接使用蘭姆達表達式設定定時器的定時槽函數. 使用信號和槽的形式調用定時器相關函數 (開始和停止
)。最后啟動線程即可.
-
- 內存釋放
m_thread->quit();m_thread->wait();m_thread->deleteLater();m_timer->deleteLater();
一般在父類或者父窗體的析構函數中執行,停止執行,釋放內存。
二、其信號和槽函數簡述
信號與槽相關知識可參考:
Qt 信號與槽的使用詳解 - 多種綁定形式、同步異步、Lambda表達式等:https://blog.csdn.net/qq_38204686/article/details/139702275
-
- 使用信號和槽的形式調用定時器相關函數
由于定時器已經被移動到線程中,所以不能直接在主線程中調用定時器相關函數。比如執行定時器停止m_timer->stop();
會顯示QObject::killTimer: Timers cannot be stopped from another thread
。
- 使用信號和槽的形式調用定時器相關函數
可參考
Qt: QTimer和QThread:https://www.cnblogs.com/lingdhox/p/4218051.html
https://stackoverflow.com/questions/53200294/qthread-with-qtimer-connection-issues
-
- 定時器開始槽函數需使用
QOverload<>::of
重載
&QTimer::start
有多個重載
函數,比如void QTimer::start(int msec)
和void QTimer::start()
,需使用QOverload<>::of
指定調用哪一個重載函數 - (在<>中指明參數,比如<int>
)。
如果只這樣寫connect(m_thread, &QThread::started, m_timer, &QTimer::start);
會報錯,
這樣connect(m_thread, SIGNAL(started()), m_timer, SLOT(start()));
是可行的,但不建議。
- 定時器開始槽函數需使用
可參考
QThread with QTimer connection issues:https://stackoverflow.com/questions/53200294/qthread-with-qtimer-connection-issues
QT-信號槽有多個重載版本{ QOverload<_>::of(&::) }:https://blog.csdn.net/ugetoneshot/article/details/139169027
三、定時器及其moveToThread簡述
-
- 創建定時器
new QTimer(nullptr)
參數parent
為空而不是this
后續需要將定時器移動到另一線程,所以其父對象需為空
- 創建定時器
void QObject::moveToThread(QThread *targetThread)
的官方解釋:
更改此對象及其子對象的線程相關性。如果對象有父對象,則無法移動該對象,事件處理將在targetThread中繼續。使用時需注意:此函數只能將對象從當前線程“推”到另一個線程,而不能將對象從任意線程“拉”到當前線程。
-
- 關于定時器的精度問題.
如下方左圖所示,近似每秒一觸發,但是誤差在2-3毫秒,設置setTimerType(Qt::PreciseTimer);
之后誤差只有1毫秒。
- 關于定時器的精度問題.
可參考
QT使用高精度定時器:https://blog.csdn.net/ljjjjjjjjjjj/article/details/130189550