Qt 定時器的幾種使用方式、windows精確定時器使用【精確度對比】

Qt 定時器的幾種方式

摘要:

  • Qt中定時器的使用有兩種方法,一種是使用QObject類提供的定時器startTimer,還有一種就是使用QTimer類。

方法介紹:

  • 共有方法:
 

QTimer(QObject *parent = Q_NULLPTR)

構造函數

 

~QTimer()

析構函數

int

interval() const

此屬性保存以毫秒爲單位的超時間隔
此屬性的默認值爲0。超時間隔爲0的QTimer將在窗口系統的事件隊列中的所有事件處理完畢後立即超時。

std::chrono::milliseconds

intervalAsDuration() const

將此計時器的間隔返回爲std::chrono::milliseconds對象。
該函數在Qt 5.8中引入。

bool

isActive() const

如果計時器正在運行(掛起),返回true;否則返回false。

bool

isSingleShot() const

此屬性保存計時器是否爲單發計時器
單發計時器只觸發一次,非單發計時器每隔毫秒觸發一次。
此屬性的默認值爲false。

int

remainingTime() const

此屬性保存剩餘時間(以毫秒爲單位)
返回計時器的剩餘值(以毫秒爲單位),直到超時。如果計時器不活動,返回的值將爲-1。如果計時器過期,返回的值將爲0

std::chrono::milliseconds

remainingTimeAsDuration() const

以std::chrono::milliseconds對象的形式返回計時器對象中剩餘的時間。如果該計時器到期或過期,返回的值是std::chrono::milliseconds::zero()。如果找不到剩餘的時間或計時器不活動,則此函數返回負持續時間。

void

setInterval(int msec)

此屬性保存以毫秒爲單位的超時間隔
此屬性的默認值爲0。超時間隔爲0的QTimer將在窗口系統的事件隊列中的所有事件處理完畢後立即超時。

void

setInterval(std::chrono::milliseconds value)

設置超時時間間隔

void

setSingleShot(bool singleShot)

此屬性保存計時器是否爲單發計時器
單發計時器只觸發一次,非單發計時器每隔毫秒觸發一次。
此屬性的默認值爲false。

void

setTimerType(Qt::TimerType atype)

控制計時器的準確性
此屬性的默認值是Qt:: crude setimer。

void

start(std::chrono::milliseconds msec)

這是一個重載函數。
啓動或重新啓動計時器,超時持續時間爲msec毫秒。
如果計時器已經在運行,它將停止並重新啓動。
如果singleShot爲真,計時器只會被激活一次。
該函數在Qt 5.8中引入。

int

timerId() const

如果計時器正在運行,則返回計時器的ID;否則返回1。

Qt::TimerType

timerType() const

控制計時器的準確性
此屬性的默認值是Qt:: crude setimer。

enum Qt::TimerType

  • 計時器類型指示計時器的精確程度,如果使用定時器要求精度相對較高的時候要通過setTimerType(Qt::TimerType atype)設置這個參數或者通過初始化傳遞此參數。

Constant

Value

Description

Qt::PreciseTimer

0

Precise timers try to keep millisecond accuracy

精確定時器試圖保持毫秒精度

Qt::CoarseTimer

1

Coarse timers try to keep accuracy within 5% of the desired interval

粗定時器試圖使精確度保持在所需間隔的5%以內

Qt::VeryCoarseTimer

2

Very coarse timers only keep full second accuracy

非常粗糙的定時器只能保持完全的秒精度

Qt定時器的使用方法

1.簡單的定時器:

 

  • 使用這個函數非常方便,因爲您不需要使用timerEvent或創建本地QTimer對象
  • 這個靜態函數在給定的時間間隔後調用一個槽。

  使用下面的方法:

  • Static Public Members:
    void singleShot(int msec, const QObject *receiver, const char *member)
    void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member)
    void singleShot(int msec, const QObject *receiver, PointerToMemberFunction method)
    void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method)
    void singleShot(int msec, Functor functor)
    void singleShot(int msec, Qt::TimerType timerType, Functor functor)
    void singleShot(int msec, const QObject *context, Functor functor)
    void singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor)
    void singleShot(std::chrono::milliseconds msec, const QObject *receiver, const char *member)
    void singleShot(std::chrono::milliseconds msec, Qt::TimerType timerType, const QObject *receiver, const char *member)

    例子:

  • 這是一次性定時器到了定時間隔之後只發送一次信號槽函數只處理一次。
  • QTimer::singleShot(600000, &app, SLOT(quit()));

     

2.QObject中的startTimer

  • int QObject::startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer)
  • 啓動計時器並返回計時器標識符,如果無法啓動計時器,則返回零。
  • 計時器事件將在每個間隔毫秒內發生,直到調用killTimer()。如果interval爲0,那麼每當不再需要處理窗口系統事件時,計時器事件就會發生一次。
  • 當計時器事件發生時,使用QTimerEvent事件參數類調用虛擬timerEvent()函數。重新實現此函數以獲取計時器事件。
  • 如果有多個計時器在運行,可以使用QTimerEvent::timerId()來查找激活的計時器。

 

  • int QObject::startTimer(std::chrono::milliseconds time, Qt::TimerType timerType = Qt::CoarseTimer)
  • 這是一個重載函數。用法與上面的一樣:
  • 如果時間等於std::chrono::duration::zero()::{ 關於介紹請看:https://zh.cppreference.com/w/cpp/chrono/duration }

  實例代碼創建步驟使用說明:

class Widget2 : public QWidget
{
    Q_OBJECT
public:
    Widget2(QWidget* parent = 0 ):QWidget(parent),
        timeID(0)
    {
        QPalette palette (this->palette());
        palette.setBrush(QPalette::Background, QBrush(QColor(100,12,130)));
        this-> setPalette( palette );
        //2. 創建定時器
        timeID = startTimer(100,Qt::PreciseTimer);

    }
    ~Widget2(){}

protected:
    // 3. 重寫定時器事件,接收定時的到來
    void timerEvent(QTimerEvent *event)
    {
        // 4. 判斷是否爲這個定時器ID(系統可能有多個定時器Id、這種方式啓動的話、全放在這裏處理)
        if(event->timerId() == timeID)
        {
            //TODO function
            if(timeID)
                killTimer(timeID);// 5.殺死定時器
            timeID = 0;
        }
    }

private:
    int timeID;//1. 聲明定時器ID
};

3.QTimer

  • QTimer類提供了重複的、單發的計時器。
  • QTimer類爲計時器提供了一個高級編程接口。要使用它,創建一個QTimer,將其timeout()信號連接到適當的插槽,並調用start()。從那時起,它將以恆定的間隔發出timeout()信號。
  • 計時器的準確性取決於底層操作系統和硬件。大多數平臺都支持1毫秒的分辨率,但在許多實際情況下,計時器的精度無法達到這個分辨率。
  • 準確度也取決於定時器的類型。對於Qt::PreciseTimer, QTimer將嘗試保持精度在1毫秒。精確的定時器也永遠不會比預期提前超時。
  • 對於Qt:: crude setimer和Qt::非常粗的timer類型,QTimer可能比預期醒得早,在這些類型的範圍內:Qt:: roughsetimer和500ms的間隔的5%內
     

  實例代碼創建步驟使用說明:

  

class Widget3 : public QWidget
{
    Q_OBJECT
public:
    Widget3(QWidget* parent = 0 ):QWidget(parent),
        m_pTimer(nullptr)
    {
        m_pTimer = new QTimer;// 2. 創建定時器對象
        m_pTimer->setTimerType(Qt::PreciseTimer); // 3.設置定時器對象精確度模式
        connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOut_Slots()));// 4. 連接定時器超時後處理的槽函數
        m_pTimer->start(100);// 5. 開啓定時器
    }
    ~Widget3(){}
public slots:
    //6. 定時器槽函數
    void timeOut_Slots()
    {
        //TODO function
        if(m_pTimer)
            killTimer(m_pTimer->timerId());//7. 殺死定時器
        delete m_pTimer;
        m_pTimer = NULL;
    }

private:
    QTimer* m_pTimer;//1. 聲明定時器對象指針
};

Windows精確定時器:

  • 這是獲取高分辨率CPU時序的常用方法。本文提出了一種更準確,可靠的解決方案,通過使用Windows API QueryPerformanceCounter和QueryPerformanceFrequency來獲取高分辨率的CPU時序。
  • QueryPerformanceFrequency() - 基本介紹
  • 類型:Win32API
  • 原型:BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
  • 作用:返回硬件支持的高精度計數器的頻率。
  • 返回值:非零,硬件支持高精度計數器;零,硬件不支持,讀取失敗。
  • QueryPerformanceFrequency() - 技術特點
  • 供WIN9X使用的高精度定時器:QueryPerformanceFrequency()和QueryPerformanceCounter(),要求計算機從硬件上支持高精度定時器。需包含windows.h頭文件。
  • 支持:屏蔽底層硬件差異(有無支持HRT的硬件high resolution timer),爲用戶提供計時功能。 如果底層硬件支持HRT,兩個API就會返回高精度
  • 的時間計數(微妙或納秒級), 如果底層硬件不支持, 兩個API的調用結果就類似於 GetTickCount () API返回給用戶的時間精度(毫秒級)。

介紹

微軟官方背景介紹:

    隨着電源管理技術在當今計算機中變得越來越普遍,RDTSC指令可能無法按預期工作,這是獲取高分辨率CPU時序的常用方法。本文提出了一種更準確,可靠的解決方案,通過使用Windows API QueryPerformanceCounter和QueryPerformanceFrequency來獲取高分辨率的CPU時序。背景建議應用兼容性背景
自從推出x86 P5指令集以來,許多遊戲開發人員都利用讀取時間戳計數器RDTSC指令來執行高分辨率計時。Windows多媒體定時器對於聲音和視頻處理來說足夠精確,但是幀時間爲十幾毫秒或更短,它們沒有足夠的分辨率來提供增量時間信息。許多遊戲在啓動時仍然使用多媒體計時器來確定CPU的頻率,並且他們使用該頻率值來縮放RDTSC的結果以獲得準確的時間。由於RDTSC的限制,Windows API通過QueryPerformanceCounter和QueryPerformanceFrequency的例程公開了訪問此功能的更正確方法。
RDTSC用於定時的這種使用受到以下基本問題的影響:
不連續的價值觀。直接使用RDTSC假定線程始終在同一處理器上運行。多處理器和雙核系統不保證核心之間的循環計數器同步。當與在不同時間空閒和恢復各種核心的現代電源管理技術相結合時,這會加劇,這導致核心通常不同步。對於應用程序,這通常會導致毛刺或潛在的崩潰,因爲線程在處理器之間跳轉並獲得導致大增量,負增量或暫停時序的定時值。
專用硬件的可用性。RDTSC將應用程序請求的定時信息鎖定到處理器的循環計數器。多年來,這是獲得高精度定時信息的最佳方式,但較新的主板現在包括提供高分辨率定時信息的專用定時設備,沒有RDTSC的缺點。
CPU頻率的可變性。通常假設CPU的頻率在程序的生命週期內是固定的。但是,使用現代電源管理技術,這是一個不正確的假設。雖然最初僅限於筆記本電腦和其他移動設備,但許多高端臺式電腦正在使用改變CPU頻率的技術; 禁用其功能以保持一致的頻率通常是用戶不能接受的。
建議遊戲需要準確的計時信息,但您還需要以避免與使用RDTSC相關的問題的方式實現計時代碼。實現高分辨率計時時,請執行以下步驟:
使用QueryPerformanceCounter和QueryPerformanceFrequency而不是RDTSC。這些API可以使用RDTSC,但可以使用主板上的定時設備或提供高質量高分辨率定時信息的一些其他系統服務。雖然RDTSC比QueryPerformanceCounter快得多,但由於後者是一個API調用,因此它是一個可以每幀調用數百次的API而沒有任何明顯的影響。(儘管如此,開發人員應該嘗試讓他們的遊戲儘可能少地調用QueryPerformanceCounter以避免任何性能損失。)
在計算增量時,應該鉗制這些值以確保定時值中的任何錯誤不會導致崩潰或不穩定的與時間相關的計算。鉗位範圍應從0(以防止負增量值)到基於最低預期幀速率的某個合理值。在您的應用程序的任何調試中,鉗位可能都很有用,但是如果進行性能分析或以某種未經優化的模式運行遊戲,請務必牢記這一點。
計算單個線程上的所有時序。計算多個線程上的時序 - 例如,每個線程與特定處理器相關聯 - 極大地降低了多核系統的性能
使用Windows API SetThreadAffinityMask將該單個線程設置爲保留在單個處理器上。通常,這是主要的遊戲主題。雖然QueryPerformanceCounter和QueryPerformanceFrequency通常針對多個處理器進行調整,但是當線程從一個處理器移動到另一個處理器時,BIOS或驅動程序中的錯誤可能導致這些例程返回不同的值。因此,最好將線程保留在單個處理器上。
所有其他線程應該運行而不收集自己的計時器數據。我們不建議使用工作線程來計算時序,因爲這將成爲同步瓶頸。相反,工作線程應該從主線程讀取時間戳,並且因爲工作線程只讀取時間戳,所以不需要使用關鍵部分。
只調用一次QueryPerformanceFrequency,因爲在系統運行時頻率不會改變。
應用兼容性
許多開發人員多年來一直對RDTSC的行爲做出假設,因此,由於時序實現,一些現有應用程序很可能在具有多個處理器或內核的系統上運行時會出現問題。這些問題通常表現爲毛刺或慢動作。對於不瞭解電源管理的應用程序沒有簡單的補救措施,但是現有的填充程序可以強制應用程序始終在多處理器系統中的單個處理器上運行。
要創建此填充程序,請從Windows應用程序兼容性下載Microsoft應用程序兼容性工具包。
使用兼容性管理器(工具包的一部分),創建應用程序和相關修訂的數據庫。爲此數據庫創建新的兼容性模式,並選擇兼容性修補程序SingleProcAffinity以強制應用程序的所有線程在單個處理器/核心上運行。通過使用命令行工具Fixpack.exe(也是工具包的一部分),您可以將此數據庫轉換爲可安裝的軟件包以進行安裝,測試和分發。
有關使用兼容性管理器的說明,請參閱工具包的文檔。有關使用Fixpack.exe的語法和示例,請參閱其命令行幫助。
有關面向客戶的信息,請參閱Microsoft幫助和支持中的以下知識庫文章:

使用用例:

LARGE_INTEGER  large_interger;
QueryPerformanceCounter(&large_interger);
c1 = large_interger.QuadPart;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章