Python从另一个线程启动/停止线程会导致意外行为

时间:2018-07-27 20:48:29

标签: python python-3.x multithreading

对如何正确地要求线程停止进行了一些研究之后,我陷入了意外行为。

我正在做一个个人项目。我的目标是在专门用于家庭医学的RaspberryPi上运行一个程序。

我的代码结构如下:

  1. 第一个线程专用于调度:每天在同一时间,我在GPIO输出上发送信号
  2. 第二个线程专用于监视键盘的手动事件
  3. 每当按下一个特定的键时,我都想启动一个专用于另一个例程的新线程,就像我的第一个线程一样

这是我的工作方式:

import schedule
from pynput import keyboard
import threading

first_thread = threading.Thread(target=heating, name="heating")
second_thread = threading.Thread(target=keyboard, name="keyboard")
first_thread.start()
second_thread.start()
stop_event = threading.Event()

我的加热程序定义为:

def heating():
    def job():
        GPIO.output(4,GPIO.HIGH)
        return

    schedule.every().day.at("01:00").do(job)

    while True:
        schedule.run_pending()
        time.sleep(0.5)

我的键盘监视器定义如下:

def keyboard():
    def on_press(key):
        if key == keyboard.Key.f4:
            shutter_thread = threading.Thread(name="shutter", target=shutter, args=(stop_event,))
            shutter_thread.start()
        if key == keyboard.Key.f5:
            stop_event.set()

     with keyboard.Listener(on_press=on_press,on_release=on_release) as listener:
        listener.join()

我的shutter线程目标类似于加热对象:

def shutter(stop_event):
    def open():
        GPIO.output(6,GPIO.HIGH)
        return

    t = threading.currentThread()

    schedule.every().day.at("22:00").do(open)
    while not stop_event.is_set():
        schedule.run_pending()
        time.sleep(0.5)

问题是每次我按下键启动shutter线程时,都会调用快门例程,但是:

  • 我的job例程中的shutter执行了两次
  • 第一个线程中的job现在每次也按计划执行两次!
  • 一旦我按下按键要求shutter线程停止,heating(第一个)线程恢复到其原始(且正确)的行为,但是shutter线程却执行了不停

我不知道为什么启动这个新线程会在另一个线程的行为上产生这种修改。为什么我的停止事件无法正常工作?

我在做什么错了?

2 个答案:

答案 0 :(得分:0)

由于您使用schedule框架来管理任务,因此一个干净的解决方案是使用相同框架的API来取消作业(而不是使用threading.Event)。这样,任务管理将保留在schedule中,而用户交互则由threading处理。

def keyboard():
    tasks = [] 

    def on_press(key):
        if key == keyboard.Key.f4:
            # Shutter task.
            tasks.append(
                schedule.every().day.at("22:00").do(lambda: GPIO.output(6,GPIO.HIGH))
            ) 
        if key == keyboard.Key.f5:
            schedule.cancel_job(tasks.pop(-1))

     with keyboard.Listener(on_press=on_press,on_release=on_release) as listener:
        listener.join()

# Heating task. 
schedule.every().day.at("01:00").do(lambda: GPIO.output(4,GPIO.HIGH))

# Start keyboard listener.
ui = threading.Thread(target=keyboard)
ui.start()

while True:
    schedule.run_pending()
    time.sleep(0.5)

答案 1 :(得分:0)

即使a_guest解决方案是干净的,我也可以为那些可能遇到类似情况的人分享第二种解决方案。

一种可行的解决方案是在不同的线程中定义特定的调度程序,而不使用默认的调度程序。

插图:

def heating():
    def job():
        GPIO.output(4,GPIO.HIGH)
        return

    heat_sched = schedule.Scheduler()
    heat_sched.every().day.at("01:00").do(job)

    while True:
        heat_sched.run_pending()
        time.sleep(1)


def shutter(stop_event):
    def open():
        GPIO.output(6,GPIO.HIGH)
        return

    shutter_sched = schedule.Scheduler()
    shutter_sched.every().day.at("22:00").do(open)

    while True:
        if not stop_event.is_set():
            shutter_sched.run_pending()
            time.sleep(0.5)
        else:
            shutter_sched.clear()
            return