Sleep(-1) in WinEventProc causes unwanted delays

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
Descolada
Posts: 1143
Joined: 23 Dec 2021, 02:30

Sleep(-1) in WinEventProc causes unwanted delays

26 Mar 2024, 00:16

Code: Select all

#Requires AutoHotkey v2 
#SingleInstance

global TotalSleepCount := 0, pCallback := CallbackCreate(HandleWinEvent, "C Fast", 7)

HandleWinEvent(hWinEventHook, event, hwnd, idObject, idChild, idEventThread, dwmsEventTime) {
    global TotalSleepCount += 1
    Sleep -1 ; commenting this fixes the issue
}

#HotIf WinActive("Notepad")
F2::
{
    global TotalSleepCount := 0
	KeyWait "F2"
    Loop 25 {
        Send "aaaaaaaaaaaaaaaaaaaa{Enter}"
        Loop 100
            Sleep(-1), TotalSleepCount++
    }
    MsgBox "Slept " TotalSleepCount " times without halting"
}
F3::
{
    global TotalSleepCount := 0
	KeyWait "F3"
    hHook := DllCall("SetWinEventHook", "UInt", EVENT_OBJECT_SHOW := 0x8002, "UInt", 0x8002, "Ptr", 0, "Ptr", pCallback, "UInt", 0, "UInt", 0, "UInt", 0)
    Loop 5
        Send "aaaaaaaaaaaaaaaaaaaa{Enter}"
    DllCall("UnhookWinEvent", "ptr", hHook)
    MsgBox "Slept " TotalSleepCount " times with halting"
}
#HotIf
Press F2 in Notepad: the line "aaaaaaaaaaaaaaaaaaaa" is quickly sent 25 times.
Press F3 in Notepad: the script halts for a bit after every "aaaaaaaaaaaaaaaaaaaa"

The only difference is using SetWinEventHook with Sleep(-1) in the callback. Removing the Sleep fixes the issue. Adding Critical -1 as the first line also fixes the issue. What could be the reason for the halts here?
lexikos
Posts: 9593
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sleep(-1) in WinEventProc causes unwanted delays

26 Mar 2024, 04:07

I'm fairly certain that whatever issue you are trying to demonstrate is not happening when I run your code. I don't see any "halting". I take it you mean there is a pause each time {Enter} is sent.

It is difficult to tell what you are trying to demonstrate, since the two hotkeys don't do the same thing, and Notepad is a terrible test subject on Windows 11. Only a fraction of what you send appears in (UWP) Notepad, and even less if Send is not reverting to SendEvent (i.e. when I close my other scripts).

Without other scripts with active hooks, what I see is:
  • F3 in Notepad: 3 to 4 "a" characters, 4 to 11 sleeps, no halting.
  • F3 in not-Notepad (a Gui with multiline Edit): the entire text appears, 1 to 7 sleeps, no halting.
With other hooks active (Send falls back to SendEvent):
  • F3 in Notepad: 9 to 12 "a" characters, 26 to 41 sleeps, no halting.
  • F3 in not-Notepad: the entire text appears, 21 to 36 sleeps, no halting.
The purpose of Sleep -1 is to make the program check its message queue. Whatever your intention, doing this may cause re-entrancy of the event hook.
Because event processing is interrupted, additional events might be received any time the hook function calls a function that causes the owning thread's message queue to get checked.
Source: Guarding Against Reentrancy in Hook Functions - Win32 apps | Microsoft Learn
You can detect re-entrancy with a simple counter, like

Code: Select all

    static Entry := 0
    Entry++
    OutputDebug Entry "`n"
    Sleep -1 ; commenting this fixes the issue
    Entry--
but I'm not sure it's actually relevant to your issue.

I would suggest timing parts of the code to confirm where the "halt" is occurring. For instance, is it during one of the Sleep -1 calls or after the callback returns (which I guess would put it within Send or one of the APIs it calls)?
Adding Critical -1 as the first line also fixes the issue.
Do you mean in the callback? Are you aware that this will affect whatever thread was running when the callback was called, because the (Fast mode) callback itself doesn't have a thread? I suppose it would effectively disable message checks during Send after the first accessibility event.

Actually, you should not be using Fast mode.
Although this performs better, it must be avoided whenever the thread from which Address is called varies (e.g. when the callback is triggered by an incoming message).
Source: CallbackCreate - Syntax & Usage | AutoHotkey v2
Descolada
Posts: 1143
Joined: 23 Dec 2021, 02:30

Re: Sleep(-1) in WinEventProc causes unwanted delays

26 Mar 2024, 04:37

I'm fairly certain that whatever issue you are trying to demonstrate is not happening when I run your code. I don't see any "halting". I take it you mean there is a pause each time {Enter} is sent.
That is exactly what is happening.
It is difficult to tell what you are trying to demonstrate, since the two hotkeys don't do the same thing, and Notepad is a terrible test subject on Windows 11. Only a fraction of what you send appears in (UWP) Notepad, and even less if Send is not reverting to SendEvent (i.e. when I close my other scripts).
F3 sends the string fewer times because otherwise the script would run too long (because of the halting). I am running Windows 10 which is why Notepad was an acceptable test subject (I'm aware of Win11 problems with Notepad), and it appears the problem doesn't exist in Windows 11. In Win 11 I also tried it in a AHK Gui with an Edit control, but couldn't reproduce the problem.
In Windows 10 Notepad, with F2 the lines where sent normally as expected. With F3 one line would be sent, then a pause, then another line, a pause etc. The same problem was described in this post (the library uses similar callback code and I narrowed the problem down to Sleep -1).
Do you mean in the callback? Are you aware that this will affect whatever thread was running when the callback was called, because the (Fast mode) callback itself doesn't have a thread? I suppose it would effectively disable message checks during Send after the first accessibility event.
Yes, in the callback. And no, I was not aware of that!

I was trying to figure out a way to launch an interruptible thread from the callback for the user, which wouldn't be considered an "emergency" and wouldn't interrupt other running threads unexpectedly. The plan was to use something like

Code: Select all

HandleWinEvent(hWinEventHook, event, hwnd, idObject, idChild, idEventThread, dwmsEventTime) {
    Critical -1
    global TotalSleepCount += 1
    SetTimer UserEvent.Bind(hwnd), -1
    Critical "Off"
    Sleep -1
}
UserEvent(hWnd) {
}
Where the Sleep -1 would cause the message queue to be checked and launch the timer callback ASAP. Without the Sleep, the timer callback could be called sometime in the tick (supposing Critical isn't active), not ASAP. Is there a better way to achieve that?

I'll investigate the problem in Windows 10 a bit more and report back...
Descolada
Posts: 1143
Joined: 23 Dec 2021, 02:30

Re: Sleep(-1) in WinEventProc causes unwanted delays

26 Mar 2024, 11:14

@lexikos I did some tests as suggested.
Starting code (ran in Windows 10, Notepad):

Code: Select all

#Requires AutoHotkey v2 
#SingleInstance

HandleWinEvent(*) {
    ;Critical -1
    ;Critical("Off")
    static Entry := 0
    Entry++
    OutputDebug Entry "`n"
    Sleep -1
    Entry--
}

Esc::ExitApp

#HotIf WinActive("Notepad")
F2::{
    pCallback := CallbackCreate(HandleWinEvent, "C Fast", 7)
    hHook := DllCall("SetWinEventHook", "UInt", EVENT_OBJECT_SHOW := 0x8002, "UInt", 0x8002, "Ptr", 0, "Ptr", pCallback, "UInt", WinGetPID("ahk_exe notepad.exe"), "UInt", 0, "UInt", 0)
    Loop 5
        Send "aaaaaaaaaaaaaaaaaaaa{Enter}"
}
output:

Code: Select all

1
2
3
4
1
2
3
4
35x 1
2
3
4
18x 1
2
3
4
18x 1
2
3
4
18x 1
+ there is halting.

1. "Fast" option removed from CallbackCreate (it does makes sense to remove it), output:

Code: Select all

1
2
3
4
1
2
3
4
1
2
3
4
1
2
3
4
1
2
3
4
86x 1
+ no halting

2. Critical -1 uncommented, "Fast" option removed

Code: Select all

1
2
3
4
1
2
3
4
1
2
3
4
5
1
2
3
4
1
2
3
4
5
84x 1
+ no halting. It seems Sleep -1 (force message check) trumps Critical -1 (disable message checks)

3. Critical("Off") uncommented, Critical -1 uncommented, "Fast" option removed

Code: Select all

1
2
3
4
18x 1
2
3
4
18x 1
2
3
4
18x 1
2
3
4
18x 1
2
3
4
18x 1
+ there is halting. This result is surprising, I would've expected the same result as test 1 (no halting).

4. Sleep -1 commented out, "Fast" option removed

Code: Select all

106x 1
+ no halting

5. Critical("Off") uncommented, Critical -1 uncommented, Sleep -1 commented out, "Fast" option removed

Code: Select all

106x 1
+ no halting

I tested in Windows 10, running a separate script:

Code: Select all

g := Gui(, "TestGui")
g.Add("Edit", "h200 w200")
g.Show()
There was no halting with any of the tests! I also tried in Windows 11 classic Notepad and also no halting.

I'm gonna write this off as Windows 10 Notepad weirdness for now, but what puzzles me is why Critical(-1), Critical("Off") would cause the seen changes.
lexikos
Posts: 9593
Joined: 30 Sep 2013, 04:07
Contact:

Re: Sleep(-1) in WinEventProc causes unwanted delays

27 Mar 2024, 03:54

Descolada wrote:
26 Mar 2024, 04:37
F3 sends the string fewer times because otherwise the script would run too long (because of the halting).
Conversely, if there's no halting, F3 will complete faster. Why send so many keystrokes in F2? Better to write it so that the expected results are the same for both. Then you can say that F3 should behave the same as F2, but for you it is much slower. Anyone can easily see that there is a difference (issue replicated), or no difference (issue not replicated).
... wouldn't interrupt other running threads unexpectedly.
Perhaps it would meet that condition, but not in any meaningful way. It would expectedly interrupt the HandleWinEvent function, which itself was an "emergency" and has already interrupted the running thread (but not started a new thread, because of Fast mode). Whatever other threads that were interrupted still can't resume til the new thread you launched finishes. At least the timer thread would not affect the settings of whatever thread HandleWinEvent interrupted.

If you want to make a new "thread" happen immediately and without risk of side-effects (like dispatching other messages), the best way is to call a callback (CallbackCreate and DllCall). There is no need to make the current thread non-Critical in that case.

Using OnMessage and SendMessage would come close, but there is more hidden complexity, and the current thread priority must be <= 0. SendMessage calls the window procedure directly if the window is owned by the same (real) thread as the caller, but I'm uncertain whether there can still be other side-effects (e.g. dispatching externally sent messages, as it does when waiting for a reply from another thread).

This result is surprising, I would've expected the same result as test 1 (no halting).
Critical being on causes some messages to be "buffered" (left in the queue), as its main effect. As documented, Critical "Off" makes the current thread immediately interruptible, regardless of whether the thread's default period of uninterruptibility has expired.

Descolada wrote:It seems Sleep -1 (force message check) trumps Critical -1 (disable message checks)
The documentation is explicit about what the numeric parameter of Critical does and does not affect.
However, functions that wait such as Sleep and WinWait will check messages regardless of this setting ...

This setting affects the following:
  • Preemptive message checks, which potentially occur before each line of code executes.
  • Periodic message checks during Send, file and download operations.
It does not affect the frequency of message checks while the script is paused or waiting.
...
Critical -1 turns on Critical but disables message checks. This does not prevent the program from checking for messages while performing a sleep, delay or wait.
Source: Critical - Syntax & Usage | AutoHotkey v2
"Critical -1" was added to the documentation in 2023, as a newly discovered feature. ;)

It is really just a consequence of (UINT)-1 producing the maximum value for UINT, the same as Critical 4294967295. In order for an automatic message check to occur, the tick count (milliseconds as an unsigned 32-bit integer) since the last check must be greater than the period specified by Critical. Even if the program could go that long without a message check (such as due to the computer being asleep for weeks), the interval can't be reached because there is no 32-bit integer greater than (UINT)-1.

There was no halting with any of the [TestGui] tests!
Notepad is more than just an Edit control. What is the callback notifying you about?

Return to “Ask for Help (v2)”

Who is online

Users browsing this forum: niCode and 40 guests