You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 09:02:31