SetTimer

AutoHotKey

SetTimer

以指定的时间间隔自动并重复运行子程序.

SetTimer [, Label, Period|On|Off|Delete, Priority]

参数

Label

跳转的目标 标签热键标签 的名称, 这样会执行 Label 下的命令, 直到遇到 ReturnExit. 和几乎其他所有命令的参数一样, Label 可以为 变量 引用 (例如 %MyLabel%), 此时将使用变量中的名称作为跳转的目标.

[v1.1.01+]: 如果省略 Label, 则使用 A_ThisLabel. 例如, SetTimer,, Off 可以用来在定时子程序中关闭计时器, 当 SetTimer,, 1000 还会更新当前定时器的 Period (周期) 或者对当前正在运行的 label 设置一个新的定时值. [v1.1.24+]: 如果 A_ThisLabel 为空但是当前线程为定时器启动的, 那么就会直接使用这个定时器. 这个特性对于用定时器调用函数或函数对象时非常方便.

[v1.1.20+]: 如果不存在有效的标签名称, 这个参数可以为一个函数的名称, 或者是引用 函数对象 的单个变量. 例如, SetTimer %funcobj%, 1000SetTimer % funcobj, 1000. 目前暂不支持其他返回对象的表达式. 详参 类的例子.

注意: [v1.1.24+]: 传递空变量或返回空值的表达式被看做一个错误. 这个参数必须为非空的值或完全忽略.

Period|On|Off|Delete

On (开): 以原来的 周期 重新启用之前禁用的计时器. 如果计时器不存在, 则进行创建 (使用默认的周期 250). 定时器也会 重置. 如果计时器存在但之前被设为 单次运行模式, 那么它仍然只运行一次.

Off (关): 禁用现有的计时器.

Delete (删除) [v1.1.20+]: 禁止并删除已存在的计时器. 如果计时器已经关联了某个 函数对象, 该对象将被释放. 关闭计时器并不会释放该(函数)对象.

周期: 使用该参数的绝对值创建或更新一个定时器,如同在此定时器执行前必须经过的 近似 的毫秒数一样.定时器会被自动启用和 重置. 定时器可以设置成重复执行或只执行一次:

  • 如果 周期 为正数, 定时器会自动重复直到被脚本确切的关闭.
  • [v1.0.46.16+]: 如果 周期 为负数, 定时器仅执行一次. 例如指定 -100 , 定时器会立即执行并在 100 ms 时关闭就像使用了 SetTimer, Label, Off 命令一样.

    [v1.1.24+]: 如果 Label 为脚本生成的对象 (不是代码中存在的函数或标签), 定时器将在定时器函数返回后自动删除, 除非定时为可开启的. 这使得对象在脚本不再引用它时可以被释放, 但是它也意味着定时器的 Period (周期)Priority (优先级) 都不会保留.

Period (周期) 必须为整数, 除非用的是变量或表达式, 在这种情况下, 任何小数部分都被忽略.周期的绝对值不能大于4294967295 ms (49.7 天).

默认:如果此参数为空并且:
1) 计时器不存在: 则使用 250 的周期进行创建.
2) 计时器已经存在: 则启用计时器并 重设 为它原来的 周期 , 除非指定了 Priority (优先级).

Priority

这个可选参数是一个介于 -2147483648 和 2147483647 之间的整数 (或为 表达式) 来表示计时器的优先级. 如果省略, 则使用 0. 请参阅 线程 了解详情.

要改变现有计时器的优先级而不影响其他方面, 请留空前一个参数.

备注

计时器很有用, 因为它们是异步运行的, 这意味着它们会以指定的频率 (间隔) 运行, 即使脚本正等待窗口, 显示对话框或忙于其他任务的时候. 它们的许多应用实例包括了当用户空闲时 (如 A_TimeIdle 反映的那样) 执行一些动作或在不需要的窗口出现时关闭它们.

尽管计时器给人一种脚本同时执行多个任务的错觉, 实际并非如此. 相反地, 定时子程序只是被当作其他线程来处理: 它们可以中断另一个线程或被另一个线程中断, 例如 热键子程序. 请参阅 线程 了解详情.

每当创建或重新启用计时器或用新的周期更新计时器时,它的子程序都不会立即运行,必须先经过它的周期 时间.如果您希望立即开始计时器的首次执行, 请使用 Gosub 来执行计时器的子程序 (但是, 这种方式不会像计时器那样启动新线程; 所以诸如 SendMode 的设置不会以它们的默认值开始).

重置:如果 SetTimer 用于现有的计时器且第二个参数为数字或单词 ON (或省略), 则计时器内部的 "上次运行的时间" 会被重置为当前时间; 换句话说, 在经过它的整个周期后子程序才会再次运行.

计时器精度: 由于操作系统中计时系统的精确度, 周期 通常会被向上取整到最近的 10 或 15.6 毫秒的倍数 (取决于所安装的硬件和驱动的类型). 例如,在 Windows NT/2000/XP 中介于 1 和 10(含边界)的周期通常相当于 10 或 15.6.使用 Loop+Sleep 可以实现更短的延时, 请参阅演示: DllCall+timeBeginPeriod+Sleep.

可靠性:计时器在下列情况中可能无法按指定频率运行:

  1. 其他程序让 CPU 高负载.
  2. 定时子程序运行的时间超过它自己的周期, 或者有太多竞争计时器 (改变 SetBatchLines 可能有帮助).
  3. 计时器已经被另一个 线程 中断, 即另一个定时子程序, 热键子程序自定义菜单项 (这可以使用 Critical 避免). 如果这种情况发生了并且中断的线程花费了很长的时间才结束, 则被中断的计时器在这段时间里会被禁用. 不过, 其他任何计时器会对这个中断首个计时器的 线程 进行中断来继续运行.
  4. 使用 CriticalThread Interrupt/Priority 后脚本会变成不可中断的. 在这样的时期, 计时器不会运行. 之后, 当脚本恢复可中断时, 任何过时的计时器会尽快运行一次然后恢复到正常的调度.

尽管计时器在脚本 挂起 时仍会运行, 但是如果 当前线程 的 "Thread NoTimers" 正在起作用或者任意线程 被暂停 时则不会. 此外, 当用户通过脚本的菜单 (例如托盘图标菜单或菜单栏) 进行导航时它们也不会运行.

因为计时器是通过临时中断脚本当前线程来运行的, 所以它们的子程序应该保持简短 (以便很快地结束), 无论何时很长的中断都是不好的.

其他备注:要在脚本运行期间保持有效的计时器一般应在 自动执行段 中进行创建. 与之相比, 临时的计时器经常可以在它自己的子程序中禁用 (请参阅此页面底部的示例).

每当计时子程序运行时,它会使用设置(例如 SendMode)的默认值开始.这些默认值可以在 自动执行段 改变.

如果 热键 的响应时间非常重要 (例如在游戏中) 且脚本包含了子程序执行时间超过 5 ms 的计时器, 那么请使用下列命令来避免 15 ms 的延迟. 如果在热键按下时恰好处于计时器线程的不可中断期间, 那么这样的延迟还是会发生.

Thread, interrupt, 0  ; 让所有的线程总是不可中断的.

如果计时器在它的子程序正运行时被禁用, 那么该子程序会继续运行直到完成.

KeyHistory 功能会显示存在的计时器数目以及当前启用的数目.

要让脚本持续运行 (例如只包含计时器的脚本), 请使用 #Persistent.

相关

Gosub, Return, 线程, Thread (命令), Critical, IsLabel(), Menu, #Persistent

示例

; 示例 #1: 当不想要的窗口出现时关闭它们:
#Persistent
SetTimer, CloseMailWarnings, 250
return

CloseMailWarnings:
WinClose, Microsoft Outlook, A timeout occured while communicating
WinClose, Microsoft Outlook, A connection to the server could not be established
return

 

; 示例 #2: 等待特定的窗口出现, 然后通知用户:
#Persistent
SetTimer, Alert1, 500
return

Alert1:
IfWinNotExist, Video Conversion, Process Complete
    return
; Otherwise:
SetTimer, Alert1, Off  ; 即此处计时器关闭自己.
SplashTextOn, , , The video conversion is finished.
Sleep, 3000
SplashTextOff
return

 

; 示例 #3: 检测热键的单次, 两次和三次按下. 这样
; 允许热键根据您按下次数的多少
; 执行不同的操作:
#c::
if winc_presses > 0 ; SetTimer 已经启动, 所以我们记录键击.
{
    winc_presses += 1
    return
}
; 否则, 这是新开始系列中的首次按下. 把次数设为 1 并启动
; 计时器:
winc_presses = 1
SetTimer, KeyWinC, -400 ; 在 400 毫秒内等待更多的键击.
return

KeyWinC:
if winc_presses = 1 ; 此键按下了一次.
{
    Run, m:\  ; 打开文件夹.
}
else if winc_presses = 2 ; 此键按下了两次.
{
    Run, m:\multimedia  ; 打开不同的文件夹.
}
else if winc_presses > 2
{
    MsgBox, Three or more clicks detected.
}
; 不论触发了上面的哪个动作, 都对 count 进行重置
; 为下一个系列的按下做准备:
winc_presses = 0
return

 

; 示例 #4: 使用 method 作为定时器子程序.

counter := new SecondCounter
counter.Start()
Sleep 5000
counter.Stop()
Sleep 2000

; 一个记录秒数的示例类...
class SecondCounter {
    __New() {
        this.interval := 1000
        this.count := 0
        ; Tick() 有一个隐式参数 "this" 引用一个对象
        ; 所以
        ; 我们需要创建一个封装 "this" 和 Tick 方法的函数来调用:
        this.timer := ObjBindMethod(this, "Tick")
    }
    Start() {
        ; 已知限制: SetTimer 需要一个纯变量引用.
        timer := this.timer
        SetTimer % timer, % this.interval
        ToolTip % "Counter started"
    }
    Stop() {
        ; 在此之前传递一个相同的对象来关闭定时器:
        timer := this.timer
        SetTimer % timer, Off
        ToolTip % "Counter stopped at " this.count
    }
    ; 本例中,定时器调用了以下方法:
    Tick() {
        ToolTip % ++this.count
    }
}

示例 #4 的备注:

  • 我们也可使用 this.timer := this.Tick.Bind(this). 当 this.timer 被调用时, 它实际上是调用 this.Tick.Call(this) 元方法 (除非 this.Tick 没有再次执行). 而 ObjBindMethod 则是创建了一个调用 this.Tick() 的对象.
  • 如果我们将 Tick 更名为 Call, 可直接使用 this 而非 this.timer. 这样也不会产生临时变量. 但 ObjBindMethod 在对象拥有多个可被不同事件源触发的方法时会很有用, 如热键, 菜单项, GUI控件等.
  • 如果定时器被自身调用的 函数/方法 修改或删除时, 它可能会简单的 忽略 Label 参数. 在某些情况下,这样就避免了需要保留原始对象传递给定时器, 这样可以省略一个临时变量 (就像上面例子中的 timer 那样).