如何在pynput中实现on_release事件的真正非阻塞?
解决pynput监听与提示音播放的非阻塞问题
嘿,我一眼就看出你的问题核心了——playsound库的默认调用是阻塞式的!它会占着当前线程直到音频播完,这就导致提示音播放期间,键盘监听器完全没法响应任何按键,不管是受试者的反应键,还是你想发起新一轮测试的启动键,都得等声音播完才行。
下面给你两种简单好用的解决方案,优先推荐第一种,完全适配你现有的代码结构:
方案一:用threading让提示音在后台播放
咱们把播放提示音的操作丢到单独的子线程里,这样主线程(负责监听键盘的那个)就能继续干活,不会被音频播放卡住。
修改后的完整代码如下:
from pynput import keyboard from datetime import datetime from playsound import playsound import threading # 先初始化启动时间变量,避免第一次按非启动键报错 initiated_time = None INITIATE = {keyboard.Key.enter} def play_sound_in_background(): # 单独封装播放逻辑,给子线程用 playsound('bong.mp3') def on_press(key): global initiated_time if key in INITIATE: initiated_time = datetime.now() print("反应测试已启动,请等待提示音后按键") else: if initiated_time is not None: pressed_time = datetime.now() # 用total_seconds()简化毫秒计算,比你原来的写法更准 reaction_time_ms = (pressed_time - initiated_time).total_seconds() * 1000 print(f'反应时间:{reaction_time_ms:.2f} ms') def on_release(key): if key in INITIATE: # 启动子线程播放提示音,主线程立马继续监听键盘 sound_thread = threading.Thread(target=play_sound_in_background) sound_thread.start() print('提示音已触发') with keyboard.Listener( on_press=on_press, on_release=on_release ) as listener: listener.join()
为啥这样管用?
线程是并行执行的,子线程负责播音频,主线程继续盯着键盘事件,两者互不干扰。另外我还给你补了initiated_time的初始化,避免第一次按非启动键时抛出错误,顺便简化了毫秒计算的写法,更简洁准确。
方案二:换个支持非阻塞播放的音频库
要是你不想用线程,也可以换用pygame.mixer这种本身就支持非阻塞播放的库,调用后直接返回,不会卡线程:
from pynput import keyboard from datetime import datetime import pygame initiated_time = None INITIATE = {keyboard.Key.enter} # 初始化pygame音频模块,提前加载音频文件 pygame.mixer.init() prompt_sound = pygame.mixer.Sound('bong.mp3') def on_press(key): global initiated_time if key in INITIATE: initiated_time = datetime.now() print("反应测试已启动,请等待提示音后按键") else: if initiated_time is not None: pressed_time = datetime.now() reaction_time_ms = (pressed_time - initiated_time).total_seconds() * 1000 print(f'反应时间:{reaction_time_ms:.2f} ms') def on_release(key): if key in INITIATE: # 非阻塞播放,调用后立刻返回,主线程不卡 prompt_sound.play() print('提示音已触发') with keyboard.Listener( on_press=on_press, on_release=on_release ) as listener: listener.join()
这个方案的好处:
pygame.mixer.Sound.play()天生就是非阻塞的,而且提前加载音频文件还能避免每次播放时的加载延迟,保证提示音能瞬间触发,不会影响反应时的准确性。
不管选哪种方案,都能实现你想要的效果:提示音一启动,受试者就能马上按键,你也能随时发起新一轮测试,完全不会被音频播放卡住啦!
内容的提问来源于stack exchange,提问作者Rome_Leader
相关产品推荐
相关产品推荐

