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

如何让pynput.keyboard.listener线程在主应用终止后仍后台运行

问题解决方案

问题分析

  1. 关闭Tkinter窗口后,mainloop()执行完毕,主进程直接退出,导致所有后台线程(包括pynput的Listener)被强制终止,来不及执行on_endsession()写入计数。
  2. 当前KeypressCounter的run()方法启动Listener后立即结束,线程本身提前退出,Listener线程成为独立线程,但主进程退出时仍会被终止。

修正代码

1. 修改counter.py

import pynput.keyboard
import threading
from threading import Event

class KeypressCounter(threading.Thread):
    def __init__(self):
        super().__init__(daemon=False)  # 设为非守护线程,保证主进程等待它完成
        self.key_count = 0 
        self.stop_event = Event()
        self.listener = None

    def run(self):
        # 创建Listener并保存引用
        self.listener = pynput.keyboard.Listener(on_press=self.on_press, daemon=False)
        self.listener.start()
        # 循环等待停止信号,避免线程提前退出
        while not self.stop_event.is_set():
            self.listener.join(0.1)  # 非阻塞等待,防止线程卡死
        # 确保Listener正确停止
        if self.listener.is_alive():
            self.listener.stop()

    def on_press(self, key):
        self.key_count += 1
        print(f"counter: {self.key_count}")
        if key == pynput.keyboard.Key.esc:
            print("esc pressed") 
            self.on_endsession()

    def on_endsession(self):
        # 写入计数到文件
        with open("key_count.txt", "w") as f:
            print("writing to file")
            f.write(str(self.key_count))
        # 触发停止信号
        self.stop_event.set()

def main():
    keypress_counter = KeypressCounter()
    keypress_counter.start()
    keypress_counter.join()  # 等待线程自然结束

if __name__ == "__main__":
    main()

2. 修改app.py

from tkinter import *
from counter import KeypressCounter

# 全局保存计数器实例,方便窗口关闭时引用
key_counter = None

def start():
    global key_counter
    # 避免重复启动线程
    if key_counter is None or not key_counter.is_alive():
        key_counter = KeypressCounter()
        key_counter.start()
        print("监听线程已启动")

def on_window_close():
    # 销毁GUI窗口,但不直接退出主进程
    window.destroy()
    # 等待后台线程自然结束(比如用户按esc触发退出)
    if key_counter is not None and key_counter.is_alive():
        key_counter.join()

window_size = '1200x600'

window = Tk()
window.geometry(window_size)
# 重写窗口关闭事件,替换默认的直接退出行为
window.protocol("WM_DELETE_WINDOW", on_window_close)

keyBoardFrame = Frame(window, width=100, height=100, bg='green')
keyBoardFrame.pack()  # 修复Frame未显示的问题

button = Button(keyBoardFrame, text='start', bg='red', fg='white', command=start)
button.grid(row=0, column=0, sticky='nsew')

window.mainloop()

关键改动说明

  • counter.py:
    • 让KeypressCounter线程循环等待停止信号,避免提前退出,绑定Listener的生命周期。
    • 确保Listener能被正确停止,防止资源泄漏。
  • app.py:
    • 用全局变量保存计数器实例,方便窗口关闭时控制线程。
    • 重写窗口关闭事件,销毁窗口后让主进程等待后台线程自然结束,而非直接退出。
    • 修复了Frame未pack导致按钮不显示的问题。

修改后,点击GUI关闭按钮,窗口会消失,但后台监听线程会持续运行,直到用户按下esc键,此时计数会自动写入key_count.txt,线程终止后主进程才会退出。

内容的提问来源于stack exchange,提问作者akashKP

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:18:19