Python keyboard模块在启动线程时冻结/无法工作

1 投票
1 回答
44 浏览
提问于 2025-04-14 17:18

这是我第一次写Python程序,我写了一个简单的Python程序,如下所示,但在调用keyboard.is_pressed()时程序卡住了,无法打印我的测试信息“loop 1 time now”。这意味着按下任何键都没有反应,这个函数调用根本没有返回,甚至Ctrl-C也无法中断程序,无法回到命令提示符。

import threading
import asyncio
import time
import keyboard

flagkeypress = False
keycode = 'a'
flagserverconnected = False

def task1():
    print("Connecting to server")
    asyncio.run(connect2svr())
    print("task1 ended")


async def connect2svr():
    global flagkeypress
    global keycode
    global flagserverconnected

    try:
        print("Connected to server")
        flagserverconnected = True

        keeploopBLE = True
        while keeploopBLE:
            if flagkeypress == True:
                flagkeypress = False
                if keycode == 'q':
                    for k in range(14, 19):
                        sk="##SP"+ f"{k:02}"
                        print(sk)
                        await asyncio.sleep(1)
                if keycode == ']':
                    keeploopBLE = False
    except Exception as e:
        print(f"Error: {e}")


print("Start of my main program")
# Create threads
t1 = threading.Thread(target=task1)
# Start threads
t1.start()

keeploop = True
while keeploop:
    print("\nOptions: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time\n")
    print("Press key for command:")

    waitkey = True
    while waitkey:
        if keyboard.is_pressed('t'):
            my_kevent = keyboard.read_event()
            keyn = my_kevent.name
            print(f"key {keyn} is pressed, program going to end")
            keycode = ']'
            flagkeypress = True
            waitkey = False
            keeploop = False
        elif keyboard.is_pressed('q'):
            my_kevent = keyboard.read_event()
            keyn = my_kevent.name
            print(f"key {keyn} is pressed")
            keycode = 'q'
            flagkeypress = True
            waitkey = False
        print("loop 1 time now")
        time.sleep(0.2)

    time.sleep(0.5)
print("Main program ended")
t1.join()

如果我去掉task1线程,下面的Python代码就简化了,然后它就像我预期的那样工作,"loop 1 time now"会不断打印出来。那么,线程task1的存在是如何影响keyboard模块的API,使其无法正常工作呢?不过到目前为止,我不能100%确定程序卡住是因为task1线程的存在,因为我之前有一个版本,在task1存在的情况下,如果我去掉一个等待的循环,它仍然可以正常工作,但我没有保存那个版本,在进一步简化到这两个版本之前。

import asyncio
import time
import keyboard

flagkeypress = False
keycode = 'a'

print("Start of my main program")
keeploop = True
while keeploop:

    print("\nOptions: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time\n")
    print("Press key for command:")

    waitkey = True
    while waitkey:
        if keyboard.is_pressed('t'):
            my_kevent = keyboard.read_event()
            keyn = my_kevent.name
            print(f"key {keyn} is pressed, program going to end")
            keycode = ']'
            flagkeypress = True
            waitkey = False
            keeploop = False
        elif keyboard.is_pressed('q'):
            my_kevent = keyboard.read_event()
            keyn = my_kevent.name
            print(f"key {keyn} is pressed")
            keycode = 'q'
            flagkeypress = True
            waitkey = False
        print("loop 1 time now")
        time.sleep(0.2)

    time.sleep(0.5)
print("Main program ended")

输出:

C:\Projects\pytest1>python pytest3.py
Start of my main program

Options: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time

Press key for command:
loop 1 time now
loop 1 time now
loop 1 time now
loop 1 time now
key q is pressed
loop 1 time now

Options: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time

Press key for command:
loop 1 time now
loop 1 time now
loop 1 time now
loop 1 time now
loop 1 time now
key q is pressed
loop 1 time now

Options: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time

Press key for command:
loop 1 time now
loop 1 time now
key q is pressed
loop 1 time now

Options: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time

Press key for command:
loop 1 time now
loop 1 time now
key q is pressed
loop 1 time now

Options: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time

Press key for command:
loop 1 time now
loop 1 time now
loop 1 time now
key q is pressed
loop 1 time now

Options: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time

Press key for command:
loop 1 time now
loop 1 time now
loop 1 time now
loop 1 time now
loop 1 time now
key q is pressed
loop 1 time now

Options: [Q]=Get Version, [T]=Get Time, [P]=Start/End Get GPS Time

Press key for command:
loop 1 time now
loop 1 time now
loop 1 time now
loop 1 time now
loop 1 time now
key t is pressed, program going to end
loop 1 time now
Main program ended

1 个回答

0

我用 keyboard.add_hotkey() 这个方法测试了一下,搭配一个回调函数,它工作得很好,不像 keyboard.is_pressed() 那样会卡住。而且我还测试了 read_key()read_event(),它们也会出现卡住和不返回的情况。总之,只有 add_hotkey() 是能正常工作的。

不过,add_hotkey() 的表现并没有我想象中那么好。它的按键缓冲区输入没有被清空,而且在键盘模块的API里没有办法清空这个缓冲区。当我的程序结束或者调用 input() 的时候,之前按下的键会被弹出来。所以这根本无法满足我的基本需求。

我又试了另一个模块 - pynput。这个模块也没有卡住或不返回的问题;但是,它同样无法清空按键输入。所有按下的键在我的程序结束时都会弹出来。

最后,我试了 msvcrt 模块(微软的)。msvcrt.kbhit()msvcrt.getch() 的表现就像在C语言中一样,非常完美。所以 msvcrt 模块完全满足了我的需求,并且在多线程和asyncio函数中也能很好地工作。

我的结论是,msvcrt的按键API是最稳定、最没有bug的;其他模块都缺乏清空按键缓冲区的能力,这真是个大缺陷。当然,最大的缺陷出现在keyboard模块,它充满了bug。

撰写回答