This is sample program I found that will allow you to toggle some loop action:
; 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
Now we can see that there's some timeout-kind-of call that starts the endless loop. The endless loop is clearly synchronous, there are no callbacks or synchronied blocks or anything.
Still, pressing F12 seems to properly stop the loop, even when it's running.
Can someone explain to me how threads are executed in autohotkey? How does it process multiple blocks of code without race conditions? Does the SetTimer
call play any role in this?
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.
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.
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...
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 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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With