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;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 9
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值