Tkinter: многопроцессорная обработка событий, происходящих в секунду

Я сделал программу Tkinter, которая читает с ведомого устройства Modbus. Каждую секунду он читает устройство и отображает вывод на этикетках. Тем не менее, у меня есть несколько вкладок, которые запускают один и тот же код для каждого подключенного устройства. Во время чтения устройства весь графический интерфейс зависает, поэтому вы не можете перемещать программу или нажимать кнопки до тех пор, пока она не завершит чтение. Поможет ли многопроцессорная обработка с зависанием? Если да, то как я могу это реализовать?

Вот мой код:

import tkinter as tk
from tkinter import *
from tkinter import ttk
from time import time
import minimalmodbus
import serial
minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True

class Page(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
    def show(self):
        self.lift()

class Page1(Page):
    def __init__(self, *args, **kwargs):
       Page.__init__(self, *args, **kwargs)

       self.gas = minimalmodbus.Instrument('COM3', 1)
       self.gas.serial.baudrate = 9600
       self.gas.serial.bytesize = 8
       self.gas.serial.parity = serial.PARITY_NONE
       self.gas.serial.stopbits = 1
       self.gas.serial.timeout = 0.25
       self.gas.mode = minimalmodbus.MODE_RTU

       self.value_display = tk.Label(self, text='value', width=10)
       self.value_display.pack(side="top")

       self.unit_display = tk.Label(self, text='unit', width=10)
       self.unit_display.pack(side="top")

       self.gas_display = tk.Label(self, text='temp', width=10)
       self.gas_display.pack(side="top")

       self.status_display = tk.Label(self, text='status', width=10)
       self.status_display.pack(side="top")

       self.command_display = tk.Label(self, text='command', width=10)
       self.command_display.pack(side="top")

       self.pressure_display = tk.Label(self, text='pressure', width=10)
       self.pressure_display.pack(side="top")

       self.timer_button = tk.Button(self, text='Start', command=self.toggle)
       self.timer_display = tk.Label(self, text='00:00', width=10)
       self.timer_button.pack(side="top")
       self.timer_display.pack(side="top")
       self.paused = True

    def gas_meth(self):
        try:
            gas_value = self.gas.read_registers(0,42)

            self.value_display.config(text=gas_value[0])
            self.unit_display.config(text=gas_value[1])
            self.gas_display.config(text=gas_value[2])
            self.status_display.config(text=gas_value[3])
            self.command_display.config(text=gas_value[4])
            self.pressure_display.config(text=gas_value[5])

        except IOError:
            self.gas_display.config(text="Lost con.")
        except ValueError:
            self.gas_display.config(text="RTU error")
        self.gas_display.after(1000, self.gas_meth)

    def toggle(self):
        if self.paused:
            self.paused = False
            self.timer_button.config(text='Stop')
            self.oldtime = time()
            self.run_timer()
            self.gas_meth()
        else:
            self.paused = True
            self.oldtime = time()
            self.timer_button.config(text='Start')

    def run_timer(self):
        if self.paused:
            return
        delta = int(time() - self.oldtime)
        timestr = '{:02}:{:02}'.format(*divmod(delta, 60))
        self.timer_display.config(text=timestr)
        self.timer_display.after(500, self.run_timer)

class MainView(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        p1 = Page1(self)

        buttonframe = tk.Frame(self)
        container = tk.Frame(self)
        buttonframe.pack(side="top", fill="x", expand=False)
        container.pack(side="top", fill="both", expand=True)

        p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
        b1 = tk.Button(buttonframe, text="Page 1", command=p1.lift)
        b1.pack(side="left")
        p1.show()

if __name__ == "__main__":
    root = tk.Tk()
    main = MainView(root)
    main.pack(side="top", fill="both", expand=True)
    root.wm_geometry("1000x600")
    root.mainloop()

1 ответ

Решение

Основная идея состоит в том, чтобы поместить код, считывающий данные с датчиков, в поток, и заставить этот код связываться с потоком GUI через очередь.

Вот очень быстрый взлом, чтобы продемонстрировать технику:

import tkinter as tk
import threading
import queue
import random
import time

class Example(object):
    def __init__(self):
        self.root = tk.Tk()
        self.sensor_vars = []

        self.root.grid_columnconfigure(1, weight=1)
        for row, i in enumerate(range(3)):
            var = tk.StringVar()
            self.sensor_vars.append(var)
            label = tk.Label(self.root, text="Sensor %d:" % i)
            value = tk.Label(self.root, textvariable=var, width=4)
            label.grid(row=row, column=0, sticky="e")
            value.grid(row=row, column=1, sticky="w")

        # create a queue for communication
        self.queue = queue.Queue()

        # create some sensors
        self.sensors = []
        for i in range(3):
            sensor = Sensor(self.queue, i)
            self.sensors.append(sensor)
            sensor.setName("Sensor %d" % i)

        # start polling the queue
        self.poll_queue()

    def start(self):
        # start the sensors
        for sensor in self.sensors:
            sensor.start()

        # start the GUI loop
        self.root.mainloop()

        # wait for the threads to finish
        for sensor in self.sensors:
            sensor.stop()
            sensor.join()

    def poll_queue(self):
        if not self.queue.empty():
            message = self.queue.get()
            index = message["index"]
            self.sensor_vars[index].set(message["value"])

        self.root.after(100, self.poll_queue)

class Sensor(threading.Thread):
    def __init__(self, queue, index):
        threading.Thread.__init__(self)
        self.queue = queue
        self.index = index
        self.stop_requested = False

    def stop(self):
        self.stop_requested = True

    def run(self):
        for i in range(10):
            if self.stop_requested:
                break
            value = random.randint(10, 100)
            self.queue.put({"index": self.index, "value": value})
            time.sleep(1)



if __name__ == "__main__":
    app = Example()
    app.start()
Другие вопросы по тегам