TLDR: Threads can interrupt each other.
Have a look at the AHK docs on Threads:
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.
New thread vs. current thread
So in your example, if F12
is pressed and toggle
is false
, it will run the loop
subroutine immediately and only once (Period of -1
). The subroutine will loop until toggle
becomes false
again.
Here comes the trick: If you press F12
again, another thread is run, and new threads interrupt the current thread by default. So the new thread will halt the loop, set toggle
to false
and then finish gracefully, since the hotkey subroutine has nothing left to do. After the hotkey subroutine has finished, the previous thread (which is our loop
timer) comes back to life. Since toggle
is now false
, it will break out of the loop and also finish...and so the circle is complete. Mind that loop
had been commanded to run only once, so no further repetition there.
Thread priorities
New threads can only interrupt the current thread if their priority is at least equal to the current thread's priority. By default, every thread has the priority of 0
, and it doesn't matter if it's a Hotkey thread, a timed subroutine, or any other kind of thread. Of course, there's an exception...
Using Sleep
The AHK docs on Sleep say:
While sleeping, new threads can be launched via hotkey, custom menu
item, or timer.
If a thread is sleeping, it basically gets interrupted and frees up all CPU time for any other thread (only for the time that it actually sleeps). That is, even threads with a lower priority can run while the current thread is sleeping. In your example, there is a substantial Sleep
of 700 ms. Of course, even without the sleep your script would work and toggle
would still be toggleable. But even if loop
had been called with a higher priority, the hokey would still be able to run while loop
is sleeping (which practically is most of the time).
The example code stinks
The code example you posted may work but IMO, is confusing and outright bad coding. The main purpose of timers is to run periodically, but here we have a loop within a timer, which defies the whole purpose of timers.
If we allow a Hotkey to spawn more than one thread, we could even use this absurd but working piece of code:
; Bad example!
#MaxThreadsPerHotkey 2
toggle := false
F12::
toggle := !toggle
while(toggle) {
SoundBeep
Sleep, 500 ; Would even work without the Sleep
}
return
Use Timers for what they are meant to do
Here's how I would implement a toggle function that left clicks every 700ms:
toggle := false
F12::
toggle := !toggle
if(toggle) {
SetTimer, DoLeftClick, Off
} else {
SetTimer, DoLeftClick, 700
}
return
DoLeftClick:
Click
return