Запуск / остановка потока Python из другого потока вызывает непредвиденное поведение

1

После некоторого исследования того, как правильно просить поток остановить, я завис в неожиданном поведении.

Я работаю над личным проектом. Моя цель - запустить программу на 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 не останавливается

Я понятия не имею, почему запуск этого нового потока дает такую модификацию в поведении другого потока. И почему мое событие остановки не работает?

Что я делаю неправильно?

  • 0
    Это содержит как минимум две ссылки на нестандартные библиотеки. Откуда идут schedule и keyboard ? Если schedule отсюда , вы вызываете run_pending() дважды из разных потоков, что выглядит а) неверно и б) racy.
  • 0
    Вы правы, я забыл упомянуть schedule библиотек отсюда и pynput отсюда, который предоставляет объект клавиатуры. Я не понимал, run_pending дважды вызывать run_pending будет неправильно, спасибо! Это решает первую проблему. Но у меня все еще есть проблема, связанная с остановкой моей темы.
Показать ещё 4 комментария
Теги:
python-3.x
multithreading

2 ответа

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню