autohotkey 的异步执行是如何工作的?
How does autohotkey's asynchronous execution work?
这是我发现的示例程序,可让您切换某些循环操作:
; This toggles the action
toggle:=false
F12::
; If true is assigned to toggle, loop starts
; It also can assign false even when loop is running
If (toggle := !toggle)
SetTimer, loop, -1
return
loop:
; Endless loop? Actually not, the value of toggle can be changed by
; another "thread" even when this loop is running
while toggle
{
Click
Sleep, 700
}
return
现在我们可以看到有一些超时类的调用启动了无限循环。无限循环显然是同步的,没有回调或同步块或任何东西。
仍然,按 F12 似乎可以正确停止循环,即使它是 运行。
谁能给我解释一下线程在 autohotkey 中是如何执行的?它如何在没有竞争条件的情况下处理多个代码块? SetTimer
电话在这方面有什么作用吗?
TLDR:线程可以相互中断。
Although AutoHotkey doesn't actually use multiple threads, it
simulates some of that behavior: If a second thread is started -- such
as by pressing another hotkey while the previous is still running --
the current thread will be interrupted (temporarily halted) to allow
the new thread to become current. If a third thread is started while
the second is still running, both the second and first will be in a
dormant state, and so on.
新线程与当前线程
因此在您的示例中,如果按下 F12
且 toggle
为 false
,它将 运行 loop
子例程 立即且仅一次(-1
期间)。子例程将循环直到 toggle
再次变为 false
。
技巧来了:再按F12
,另一个线程是运行,新线程默认中断当前线程。因此,新线程将停止循环,将 toggle
设置为 false
,然后优雅地完成,因为热键子例程无事可做。热键子例程完成后,前一个线程(即我们的 loop
计时器)恢复生机。由于 toggle
现在是 false
,它将跳出循环并完成...因此循环完成。请注意 loop
仅被命令 运行 一次,因此不再重复。
线程优先级
新线程只有在 priority 至少等于当前线程的优先级时才能中断当前线程。默认情况下,每个线程都有 0
的优先级,无论它是 Hotkey 线程、定时子程序还是任何其他类型的线程都没有关系。当然也有例外...
使用Sleep
While sleeping, new threads can be launched via hotkey, custom menu
item, or timer.
如果一个线程正在休眠,它基本上会被中断并释放所有 CPU 时间 给任何其他线程(仅用于它实际休眠的时间)。也就是说,即使是优先级较低的线程也可以在当前线程休眠时 运行 。在您的示例中,有 700 毫秒的大量 Sleep
。当然,即使没有睡眠,您的脚本也可以运行,并且 toggle
仍然可以 切换 。但是即使 loop
以更高的优先级被调用,当 loop
正在睡觉时(这实际上是大部分时间),hokey 仍然能够 运行。
示例代码很烂
您发布的代码示例可能有效,但在我看来,它令人困惑且完全是错误的编码。定时器的主要目的是周期性地运行,但是这里我们在定时器中有一个循环,这违背了定时器的全部目的。
如果我们允许热键生成多个线程,我们甚至可以使用这段荒谬但有效的代码:
; Bad example!
#MaxThreadsPerHotkey 2
toggle := false
F12::
toggle := !toggle
while(toggle) {
SoundBeep
Sleep, 500 ; Would even work without the Sleep
}
return
将定时器用于它们应该做的事情
以下是我如何实现每 700 毫秒左键单击一次的切换功能:
toggle := false
F12::
toggle := !toggle
if(toggle) {
SetTimer, DoLeftClick, Off
} else {
SetTimer, DoLeftClick, 700
}
return
DoLeftClick:
Click
return
不要认为这是一个完整的答案。
我只想补充一点,从 v1.1.20 开始,您应该几乎总是使用函数而不是标签。这避免了许多潜在的冲突(标签在全局范围内执行)。
所以最好你会这样做:
F12::
__F12() {
Static toggle := False
toggle := !toggle
SetTimer, DoLeftClick, % toggle ? 700 : "Off"
}
DoLeftClick() {
Click
}
这是我发现的示例程序,可让您切换某些循环操作:
; This toggles the action
toggle:=false
F12::
; If true is assigned to toggle, loop starts
; It also can assign false even when loop is running
If (toggle := !toggle)
SetTimer, loop, -1
return
loop:
; Endless loop? Actually not, the value of toggle can be changed by
; another "thread" even when this loop is running
while toggle
{
Click
Sleep, 700
}
return
现在我们可以看到有一些超时类的调用启动了无限循环。无限循环显然是同步的,没有回调或同步块或任何东西。
仍然,按 F12 似乎可以正确停止循环,即使它是 运行。
谁能给我解释一下线程在 autohotkey 中是如何执行的?它如何在没有竞争条件的情况下处理多个代码块? SetTimer
电话在这方面有什么作用吗?
TLDR:线程可以相互中断。
Although AutoHotkey doesn't actually use multiple threads, it simulates some of that behavior: If a second thread is started -- such as by pressing another hotkey while the previous is still running -- the current thread will be interrupted (temporarily halted) to allow the new thread to become current. If a third thread is started while the second is still running, both the second and first will be in a dormant state, and so on.
新线程与当前线程
因此在您的示例中,如果按下 F12
且 toggle
为 false
,它将 运行 loop
子例程 立即且仅一次(-1
期间)。子例程将循环直到 toggle
再次变为 false
。
技巧来了:再按F12
,另一个线程是运行,新线程默认中断当前线程。因此,新线程将停止循环,将 toggle
设置为 false
,然后优雅地完成,因为热键子例程无事可做。热键子例程完成后,前一个线程(即我们的 loop
计时器)恢复生机。由于 toggle
现在是 false
,它将跳出循环并完成...因此循环完成。请注意 loop
仅被命令 运行 一次,因此不再重复。
线程优先级
新线程只有在 priority 至少等于当前线程的优先级时才能中断当前线程。默认情况下,每个线程都有 0
的优先级,无论它是 Hotkey 线程、定时子程序还是任何其他类型的线程都没有关系。当然也有例外...
使用Sleep
While sleeping, new threads can be launched via hotkey, custom menu item, or timer.
如果一个线程正在休眠,它基本上会被中断并释放所有 CPU 时间 给任何其他线程(仅用于它实际休眠的时间)。也就是说,即使是优先级较低的线程也可以在当前线程休眠时 运行 。在您的示例中,有 700 毫秒的大量 Sleep
。当然,即使没有睡眠,您的脚本也可以运行,并且 toggle
仍然可以 切换 。但是即使 loop
以更高的优先级被调用,当 loop
正在睡觉时(这实际上是大部分时间),hokey 仍然能够 运行。
示例代码很烂
您发布的代码示例可能有效,但在我看来,它令人困惑且完全是错误的编码。定时器的主要目的是周期性地运行,但是这里我们在定时器中有一个循环,这违背了定时器的全部目的。 如果我们允许热键生成多个线程,我们甚至可以使用这段荒谬但有效的代码:
; Bad example!
#MaxThreadsPerHotkey 2
toggle := false
F12::
toggle := !toggle
while(toggle) {
SoundBeep
Sleep, 500 ; Would even work without the Sleep
}
return
将定时器用于它们应该做的事情
以下是我如何实现每 700 毫秒左键单击一次的切换功能:
toggle := false
F12::
toggle := !toggle
if(toggle) {
SetTimer, DoLeftClick, Off
} else {
SetTimer, DoLeftClick, 700
}
return
DoLeftClick:
Click
return
不要认为这是一个完整的答案。
我只想补充一点,从 v1.1.20 开始,您应该几乎总是使用函数而不是标签。这避免了许多潜在的冲突(标签在全局范围内执行)。
所以最好你会这样做:
F12::
__F12() {
Static toggle := False
toggle := !toggle
SetTimer, DoLeftClick, % toggle ? 700 : "Off"
}
DoLeftClick() {
Click
}