Я работаю с Raspberry Pi.
У меня есть кнопка, подключенная к выводу GPIO, и светодиод, подключенный к другому выводу. Когда кнопка нажата, вызывается функция. Пока эта функция активна, я хочу, чтобы светодиод мигал, что требует фоновой резьбы. По сути, это означает, что мне нужен фоновый поток для запуска во время работы моего обработчика кнопок и остановки, когда мой обработчик кнопок останавливается.
Проблема, которую я имею, демонстрируется запуском приведенного ниже кода. Код начинается с одного потока, но когда я нажимаю кнопку, threading.active_count() показывает, что запущено 3 потока (а не 2, как ожидалось). Когда поток завершен, у меня остаются 2 фоновых потока, а не 1, как ожидалось.
Это мой код:
#!/usr/bin/env python3
import RPi.GPIO as GPIO
import time
import threading
from threading import Thread, Event
#########################
# Function to Blink LED #
#########################
# Sample function that blinks the LED
def blink_led_func(led, stop_blinking):
while not stop_blinking.is_set():
print("Blinking LED...")
time.sleep(0.5)
#############
# Decorator #
#############
# Starts a background thread which blinks the LED, runs the decorated
# function, and when the function is done running, stops blinking the LED
class blink_led:
def __init__(self, function):
self.f = function
def __call__(self, channel):
stop = Event()
t = Thread(target=blink_led_func, args=(1, stop))
t.start()
self.f(channel)
stop.set()
t.join()
##################
# Button Handler #
##################
# Called when button is pressed
@blink_led
def btn_handler(channel):
print("Button pressed")
time.sleep(5)
##############
# Setup GPIO #
##############
# Setup pin
GPIO.setmode(GPIO.BOARD)
GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_UP)
##############################
# Add Button Event Listeners #
##############################
GPIO.add_event_detect(12, GPIO.FALLING, callback=btn_handler, bouncetime=300)
########
# Main #
########
print("Listening for button presses...")
i = 0
while True:
time.sleep(1)
print("%s threads running" % threading.active_count())
Это вывод моего кода:
Listening for button presses...
1 threads running
1 threads running
1 threads running
Blinking LED...
Button pressed
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
Button pressed
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
Blinking LED...
3 threads running
Blinking LED...
2 threads running
2 threads running
2 threads running
Это сбивает меня с толку, потому что в моем реальном коде у меня есть обработчик Ctrl + C, который говорит: используйте threading.Event(), чтобы сообщить всем потокам о смерти, дождитесь active_count() == 1
(остался только основной поток).), очистите GPIO и выйдите. Теоретически это должно предотвратить попытки фоновых потоков использовать библиотеку GPIO для мигания после очистки (что может вызвать исключение), но на практике она застревает в ожидании смерти других потоков, как всегда 2 по какой-то причине.
Я сделал что-то неправильно? Или библиотека GPIO делает что-то напуганное?
Изменение: Если я закомментирую строку GPIO.add_event_detect
и добавлю ручной вызов для моей функции btn_handler(1)
), у меня нет этой проблемы. После того, как функция завершена, я active_count()
на 1 поток согласно active_count()
. Какова бы ни была проблема, похоже, это связано со мной, когда я запускаю поток в функции обработчика событий GPIO.
Также обратите внимание, что если я не запускаю фоновый поток в btn_handler, active_count()
остается active_count()
1 на протяжении всего цикла, поэтому, насколько я могу судить, библиотека GPIO не запускает никаких фоновых потоков.
Редактировать 2: Также обратите внимание, что когда у меня до 2 запущенных потоков (когда я ожидаю, что будет только один), если я добавляю код для проверки имен потоков, дополнительный поток называется "Dummy-3"
RPi.GPIO
событий RPi.GPIO
выполняется в специальном потоке, запущенном неявно для обработки обратных вызовов:
RPi.GPIO запускает второй поток для функций обратного вызова. Это означает, что функции обратного вызова могут быть запущены одновременно с вашей основной программой, в немедленном ответе на ребро.
Существует только один из этих потоков, независимо от того, сколько зарегистрировано обратных вызовов:
[T] функции обратного вызова выполняются последовательно, а не одновременно. Это связано с тем, что для обратных вызовов используется только один поток, в котором выполняется каждый обратный вызов в том порядке, в котором они были определены.
GPIO.add_event_detect
?