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

树莓派摩尔斯电码输入程序线程无法终止问题求助

问题:树莓派摩尔斯电码程序无法通过全局变量终止按钮监听线程

我正在开发一款运行在树莓派上的摩尔斯电码输入程序,设置了3个按钮,分别对应点、划以及用于标记字母输入结束的指令键。为实现同时监听三个按钮的输入,我使用了线程技术,但希望能够停止按钮监听功能。

我尝试通过全局变量n来控制线程:按下指令键时将n设为10,本应终止所有线程中的while n == 0循环,但实际n确实被设为10后,循环仍持续运行,完全忽略n == 0的条件。我试过加入sys.exit()等方式,也调整过n的条件判断,但都没有效果,程序仍继续接收按钮输入。

以下是我的代码:

from gpiozero import Button
from threading import Thread
import sys
btn1 = Button(2)
btn2 = Button(3)
cmdbtn = Button(4)
currentMorse = ""

def pressed1():
    print("Pressed1")
    global currentMorse
    currentMorse = currentMorse + "."
    print(currentMorse)

def released1():
    print("Released")

def pressed2():
    print("Pressed2")
    global currentMorse
    currentMorse = currentMorse + ","
    print(currentMorse)

def released2():
    print("Released")

def pressedcmd():
    print("Pressedcmd")

def releasedcmd():
    print("Releasedcmd")
    global n
    n = 10

def checker1():
    global n
    while n == 0:
        btn1.when_pressed = pressed1
        btn1.when_released = released1
    print(n)

def checker2():
    global n
    while n == 0: 
        btn2.when_pressed = pressed2
        btn2.when_released = released2
    print(n)

def checkercmd():
    global n
    while n == 0:         
        cmdbtn.when_pressed = pressedcmd
        cmdbtn.when_released = releasedcmd
    print(n)

if __name__ == '__main__':
    n = 0
    Thread(target = checker1).start()
    Thread(target = checker2).start()
    Thread(target = checkercmd).start()

问题分析与解决方案

错误原因

  1. GPIOZero回调的持续性:你在while循环里反复设置when_pressed和when_released,但GPIOZero的回调是一次设置终身生效的,只要不主动取消,按钮事件就会一直触发,和你的线程循环完全无关。哪怕循环终止,之前设置的回调依然会响应按钮操作。
  2. 线程循环逻辑无效:你的while n == 0循环里没有任何阻塞或等待逻辑,会以极高频率重复执行设置回调的操作,不仅占用CPU,还导致线程根本没机会及时读取全局变量n的最新值,循环自然无法终止。
  3. 全局变量控制逻辑错位:你想用全局变量终止线程循环,但核心问题是循环本身对按钮监听没有实际控制作用,就算循环终止,回调依然在运行。

修复方案

核心思路

  • 放弃线程循环反复设置回调的方式,GPIOZero本身支持异步监听按钮,只需初始化设置一次回调即可。
  • 用threading.Event替代全局变量,更安全地控制线程和事件处理状态。
  • 按下指令键时,不仅触发停止信号,还要主动取消所有按钮的回调,彻底终止监听。

修改后的代码示例

from gpiozero import Button
from threading import Event
import sys

btn1 = Button(2)
btn2 = Button(3)
cmdbtn = Button(4)
currentMorse = ""
# 用Event对象实现线程安全的停止信号
stop_event = Event()

def pressed1():
    # 只有未停止时才处理输入
    if not stop_event.is_set():
        print("Pressed1")
        global currentMorse
        currentMorse += "."
        print(currentMorse)

def released1():
    if not stop_event.is_set():
        print("Released")

def pressed2():
    if not stop_event.is_set():
        print("Pressed2")
        global currentMorse
        currentMorse += "-"  # 改用"-"表示划,符合摩尔斯电码规范
        print(currentMorse)

def released2():
    if not stop_event.is_set():
        print("Released")

def pressedcmd():
    if not stop_event.is_set():
        print("Pressedcmd")

def releasedcmd():
    print("Releasedcmd")
    # 触发停止信号
    stop_event.set()
    # 取消所有按钮的回调,彻底终止监听
    btn1.when_pressed = None
    btn1.when_released = None
    btn2.when_pressed = None
    btn2.when_released = None
    cmdbtn.when_pressed = None
    cmdbtn.when_released = None
    print("已停止按钮监听")

# 仅初始化一次回调即可
btn1.when_pressed = pressed1
btn1.when_released = released1
btn2.when_pressed = pressed2
btn2.when_released = released2
cmdbtn.when_pressed = pressedcmd
cmdbtn.when_released = releasedcmd

if __name__ == '__main__':
    try:
        # 主程序阻塞等待停止信号
        stop_event.wait()
    except KeyboardInterrupt:
        # 处理Ctrl+C强制退出的情况
        stop_event.set()
        # 清理回调
        btn1.when_pressed = None
        btn1.when_released = None
        btn2.when_pressed = None
        btn2.when_released = None
        cmdbtn.when_pressed = None
        cmdbtn.when_released = None
        print("程序已退出")

关键改进点

  • 用threading.Event替代全局变量,线程间状态同步更可靠。
  • 每个回调函数先检查停止状态,避免停止后仍处理按钮事件。
  • 按下指令键时主动取消所有回调,彻底终止按钮监听。
  • 主程序通过stop_event.wait()保持运行,同时支持Ctrl+C安全退出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 22:35:02