Beinje
Beinje

Reputation: 626

Python starting/stopping thread from another thread causes unexpected behavior

After some research on how to properly ask a thread to stop, I am stuck into an unexpected behavior.

I am working on a personal project. My aim is to run a program on a RaspberryPi dedicated to domotics.

My code is structured as below:

  1. a first thread is dedicated to scheduling : everyday at the same hour, I send a signal on GPIO output
  2. a second thread is dedicated to monitoring keyboard for manual events
  3. whenever a specific key is pressed, I want to start a new thread that is dedicated to another routine just like my first thread

Here is how I proceed:

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()

My heating routine is defined by:

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)

My keyboard monitor is defined as follow:

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()

My shutter thread target is similar to the heating one:

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)

Problem is everytime I press the key to start my shutter thread, the shutter routine is called but:

I have no idea why starting this new thread yields such modification in the behaviour of the other thread. And why my stopping event is not working ?

What am I doing wrong ?

Upvotes: 3

Views: 152

Answers (2)

Beinje
Beinje

Reputation: 626

Even if a_guest solution is a clean one, I can share a second solution for those who can face a similar situation.

A working solution is to define a specific scheduler in the different threads instead of using the default one.

Illustration:

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

Upvotes: 1

a_guest
a_guest

Reputation: 36329

Since you are using the schedule framework for managing tasks a clean solution would be to use the same framework's API for canceling jobs (instead of using threading.Event). That way tasks management remains within schedule and user interaction is handled by 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)

Upvotes: 1

Related Questions