У меня есть приложение, написанное с использованием пользовательского интерфейса tkinter, которое выполняет реальные действия через стек GPIO.
В качестве примера представьте, что мое приложение имеет датчик температуры, который проверяет температуру в помещении, а затем имеет внешний аппаратный интерфейс с системой кондиционирования воздуха для регулировки выходной температуры системы HVAC. Это делается простым способом через стек GPIO.
Приложение имеет некоторые действия типа «критически важные для безопасности», которые оно может выполнять. Существует множество аппаратных блокировок, но я по-прежнему не хочу, чтобы приложение вышло из строя и заставило кондиционер работать на полную мощность и превратить мой теоретический дом в морозильную камеру.
Проблема всегда в том... как преобразовать приложение, управляемое событиями графического пользовательского интерфейса, в приложение с графическим интерфейсом, в котором также выполняется еще один цикл без:
Итак, я остановился на следующей архитектуре:
импортировать tkinter как tk
def main(args):
# Build the UI for the user
root = tk.Tk()
buttonShutDown = tk.Button(root, text = "PUSH ME!")#, command=sys.exit(0))
buttonShutDown.pack()
# Go into the method that looks at inputs and outputs
monitorInputsAndActionOutputs(root)
# The main tkinter loop. NOTE, there are about 9000 articles on why you should NOT just shove everyhtnig in a loop and call root.update(). This is the corect way of doing it! Use root.after() calls!
root.mainloop()
def monitorInputsAndActionOutputs(root):
print ("checked the room temperature")
print ("adjusted the airconditioning to make comfy")
root.after(100, monitorInputsAndActionOutputs, root) # readd one event of the monitorInputsAndActionOutputs process to the tkinter / tcl queue
return None
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv))
На самом деле это прекрасно работает, и теоретически будет работать вечно, и каждый будет чувствовать себя комфортно(TM).
Тем не менее, я вижу потенциальную проблему в том, что приложение имеет приличное количество методов, выполняющих различные настройки стиля PID с замкнутым циклом (чтение, размышление, изменение, итерация), и я обеспокоен тем, что очередь событий tkinter начнет засоряться обработкой после (), и вся система либо замедлится, либо полностью сломается, либо заморозит пользовательский интерфейс, либо произойдет комбинация всех трех факторов.
Итак, я хочу добавить что-то вроде (грубый пример кода, это, конечно, не работает):
def (checkQueueDepth):
myQueue = root.getQueueLength()
if (myQueue > 100 items):
doSomethingToAlertSomeone()
deprioritiseNonCriticalEventsTillThingsCalmTFDown()
Чтобы реализовать что-то подобное, мне нужно программно получить глубину очереди root/tkinter.
После продолжительного почесывания бороды пробираюсь через
Я установил, что внешний модуль не может запросить длину очереди tk, который основан на библиотеке tcl и, вероятно, реализует или расширяет их уведомитель .
Судя по коду tkinter, также не похоже, что существует какой-либо частный метод или объект/переменная, к которому я могу проникнуть через парадную дверь и допросить.
В-третьих, единственное «решение», которое это выглядит, — это потенциально реализовать новую систему уведомлений для TCL, а затем расширить ее функциональность для обеспечения этой функции. У меня нет шансов сделать это, у меня нет ни знаний, ни времени, и я бы скорее переписал все свое приложение на другом языке.
Поскольку это невозможно сделать «правильным» способом, я подумал о способе «мы не собираемся называть это неправильным, но вполне могли бы и быть».
То, что я придумал, работает примерно так:
def checkCodeLoops(root):
# The maximum time we will accept the queue processing to blow out to is 3 seconds.
maxDelayInQueueResponse = datetime.timedelta(seconds = 3)
# Take a note of the time now as well as the last time this method was run
lastCheckTime = checkTime
checkTime = datetime.datetime.now()
# If the time between this invocation of the checkCodeLoops method and the last one is greater than the max allowable time, panic!
if (checkTime - lastCheckTime > maxDelayInQueueResponse):
doSomethingToReduceLoad()
lockUISoUserCantAddMoreWorkToTheQueue()
# reque this method so that we can check again in 100ms (theoretically in 100ms at least - if the queue's flooded then this might never fire)
root.after_idle(100, monitorInputsAndActionOutputs, root)
return None
В общем, это дерьмовый способ сделать это. По сути, вы добавляете работу в очередь, чтобы увидеть, занята ли очередь (глупо), а также есть еще одна проблема: если очередь переполнена, то этот код на самом деле не запустится, потому что любые события, добавленные в процесс after_idle(), являются обрабатывается только тогда, когда в очереди нет after() и других элементов с более высоким приоритетом.
Поэтому я мог бы изменить его на root.after вместо root.after_idle, и теоретически это помогло бы, но у нас все еще остается проблема с использованием события в очереди, чтобы проверить, не работает ли очередь, что довольно глупо.
Если бы у меня был способ проверить глубину очереди, я мог бы начать реализовывать стратегии управления нагрузкой ДО того, как дело дошло до стадии паники.
Очень хотелось бы услышать, есть ли у кого-нибудь лучший способ сделать это.
Я не уверен, насколько это полезно, но вы можете использовать метод after info
, чтобы получить все установленные события after
и after idle
. Получить их можно, написав примерно так: root.call("after", "info")
. Опять же, возможно, я не понял, о чем вы спросили.
@TheLizzard — запускаю это на платформе i386 нижнего уровня, но хотел бы, чтобы мой код и дизайн были переносимы на стек Raspberry Pi на базе ARM. Раньше использовали RPI, и циклы while(run) выполняли функции PID с замкнутым контуром и снижались до 30 циклов в секунду. Однако мой код в этом приложении был не очень оптимизирован.
@DanyaK - я поиграю с этим, но не уверен, что это поможет, потому что теоретически это покажет мне, что у меня есть 10 событий after для моих 10 программ проверки цикла кода, но это не покажет мне, что в нем есть 1000 других событий очередь, требующая обработки. Но это только начало, спасибо за ваш вклад, я посмотрю, что можно с этим сделать. Может быть, я смогу получить список событий после, а затем узнать, как долго они находились в очереди?
@LlamaDelRae Метод after info
показывает только все события after
и after idle
, которые в данный момент находятся в очереди, но я думаю, что у вас нет возможности узнать, как долго они там находятся.
@DanyaK, понимаю, но все же может быть полезно, так как, возможно, я мог бы использовать его, чтобы получить события как объект, а затем запросить события, чтобы узнать, когда они запускались в последний раз? Похоже, IDK добавляет много обработки, чтобы проверить, не слишком ли много обработки..... Мне придется подумать об этом и о последствиях, прежде чем я схожу с ума от написания кода и достижения verschlimmbesserung ссылка
К сожалению, нет никакого способа узнать это. В тот момент, когда вы можете задать вопрос, многие источники событий знают только, есть ли хотя бы одно ожидающее событие (которое соответствует одному событию ОС), и все системные вызовы способны принимать несколько событий из соответствующего источника в этот момент. (и может делать это безопасно благодаря неблокирующему вводу-выводу). Там действительно сложно!
Что вы можете сделать, так это отслеживать, происходят ли между обычными событиями таймера событие простоя. События простоя срабатывают, когда другим источникам событий нечего внести, и альтернативой является переход в спящий режим в системном вызове select()
. (Или poll()
или что-то еще, на что настроен уведомитель, в зависимости от платформы и параметров сборки.) Если между этими обычными событиями таймера обслуживаются события простоя, очередь работоспособна; в противном случае вы находитесь в ситуации переполнения.
Теория массового обслуживания говорит нам, что пока потребители идут в ногу с производителями, размер очереди будет стремиться к нулю. Когда все наоборот, размер очереди стремится к бесконечности. (Точная балансировка очень маловероятна.)
Что ж.....это очень полезный комментарий, спасибо. Возник очень важный момент, который я не учел - вероятность сбалансированной очереди равна нулю (как вы говорите, они будут либо стремиться к нулю, либо к бесконечности). Это важное соображение, поскольку оно означает, что теоретически вы можете проверить работоспособность очереди, просто используя дизайн after_idle, который я опубликовал выше. Из-за характера тенденций вы можете предположить, что если обрабатываются задачи after_idle, очередь хорошая, а если нет, то очередь плохая, НЕЗАВИСИМО от того, сколько элементов находится в q. Спасибо, сэр!
Другой подход к источникам событий: это генераторы (часто связанные с ОС), а не очереди.
На чем ты это делаешь? Сколько времени вам требуется для считывания и обработки данных с датчиков? Обычно, пока вы не выполняете нелепый объем работы (или не блокируете операции ввода-вывода), вы можете использовать имеющийся у вас код.