Go协程调用BlockInput后无法恢复输入的原因及解决方案
Go协程调用BlockInput无法恢复输入的原因及解决方案
原因
Windows API BlockInput 有一个关键限制:必须由调用 BlockInput(true) 的同一个系统线程来调用 BlockInput(false),才能解除输入屏蔽。
Go的协程采用M:N调度模型,协程会被Go runtime动态调度到不同的系统线程上执行。当你在一个协程里调用 BlockInput(true) 后,后续用于恢复的 BlockInput(false) 如果在另一个协程(主协程或其他协程)执行,大概率会被调度到另一个系统线程,导致恢复操作无效。而直接在主协程连续调用时,两次调用都在同一个系统线程,因此可以正常恢复。
解决方案
方案1:绑定协程到固定系统线程
使用 runtime.LockOSThread() 将执行屏蔽/恢复逻辑的协程绑定到一个固定的系统线程,确保两次 BlockInput 调用在同一线程执行:
package main import ( "runtime" "syscall" "time" ) var blockInput = syscall.MustLoadDLL("user32.dll").MustFindProc("BlockInput") func main() { done := make(chan struct{}) go func() { // 锁定协程到当前系统线程,后续该协程的所有操作都在这个线程执行 runtime.LockOSThread() defer runtime.UnlockOSThread() // 执行输入屏蔽 if ret, _, err := blockInput.Call(true); ret == 0 { panic(err) } // 等待恢复信号 <-done // 执行输入恢复 if ret, _, err := blockInput.Call(false); ret == 0 { panic(err) } }() // 模拟业务逻辑执行时间 time.Sleep(3 * time.Second) // 发送恢复信号 close(done) time.Sleep(5 * time.Second) }
方案2:在同一线程(主协程)执行完整逻辑
如果不需要异步执行屏蔽逻辑,直接在主协程中连续调用 BlockInput(true) 和 BlockInput(false),确保两次调用在同一系统线程:
package main import ( "syscall" "time" ) var blockInput = syscall.MustLoadDLL("user32.dll").MustFindProc("BlockInput") func main() { // 屏蔽输入 blockInput.Call(true) // 模拟业务逻辑 time.Sleep(3 * time.Second) // 恢复输入 blockInput.Call(false) time.Sleep(5 * time.Second) }
注意事项
BlockInput仅在调用它的线程存活期间有效,如果调用BlockInput(true)的线程意外终止,系统会自动恢复输入。- 在Windows Vista及以上系统中,程序需要具备UAC权限才能成功调用
BlockInput,否则会调用失败。
内容的提问来源于stack exchange,提问作者Psyyyy
相关产品推荐
相关产品推荐

