我正在尝试构建一个支持多处理的Tkinter程序。我需要从多个 Modbus 设备读取并将输出显示在 GUI 上。
我已经通过使用进程通过命令行成功地完成了此操作,但是在 Tkinter 中,每次我尝试读取时我的 GUI 都会冻结。
这是我的代码:
import os
from multiprocessing import Process
import threading
import queue
import tkinter as tk
from tkinter import *
from tkinter import ttk
import time
import time as ttt
import minimalmodbus
import serial
minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.gas = minimalmodbus.Instrument('COM3', 1)
self.gas.serial.baudrate = 9600
self.gas.serial.parity = serial.PARITY_NONE
self.gas.serial.bytesize = 8
self.gas.serial.stopbits = 1
self.gas.serial.timeout = 0.25
self.gas.mode = minimalmodbus.MODE_RTU
self.pack()
self.create_widgets()
def create_widgets(self):
self.first_gas_labelframe = LabelFrame(self, text="Gas 1", width=100)
self.first_gas_labelframe.pack()
self.value_label = Label(self.first_gas_labelframe, text="Value")
self.value_label.pack()
self.unit_label = Label(self.first_gas_labelframe, text="Unit")
self.unit_label.pack()
self.temp_label = Label(self.first_gas_labelframe, text="Temp")
self.temp_label.pack()
self.timer_button = tk.Button(self, text='Start', command=self.process)
self.quit = tk.Button(self, text="QUIT", fg="red", command=root.destroy)
self.quit.pack()
self.gas_list = [self.gas]
def reader():
self.read = gas_num.read_registers(0,42)
self.value_label.config(text=self.read[0])
self.unit_label.config(text=self.read[1])
self.temp_label.config(text=self.read[2])
def process(self):
for sen in self.gas_list:
self.proc = Process(target=self.reader, args=(sen,))
self.proc.start()
self.proc.join()
if __name__ == '__main__':
root = tk.Tk()
app = Application()
app.mainloop()
当我按下开始按钮时,程序将冻结,直到该过程完成。如何正确设置系统以在运行进程的同时使 GUI 正常运行?
最简单的解决方案是将所有这些放在单独的线程上。您的 GUI 冻结的原因是process
方法消耗了主线程,在它完成之前,Tkinter 不会更新任何内容。
下面是一个简单的示例:
self.timer_button = tk.Button(self, text='Start', command=lambda: threading.Thread(target=self.process).start())
但是,这不会阻止用户单击该按钮两次。您可以创建一个控制此操作的新方法。例:
import os
from multiprocessing import Process
import threading
import queue
import tkinter as tk
from tkinter import *
from tkinter import ttk
import time
import time as ttt
import minimalmodbus
import serial
minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True
THREAD_LOCK = threading.Lock()
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.gas = minimalmodbus.Instrument('COM3', 1)
self.gas.serial.baudrate = 9600
self.gas.serial.parity = serial.PARITY_NONE
self.gas.serial.bytesize = 8
self.gas.serial.stopbits = 1
self.gas.serial.timeout = 0.25
self.gas.mode = minimalmodbus.MODE_RTU
self.pack()
self.create_widgets()
def create_widgets(self):
self.first_gas_labelframe = LabelFrame(self, text="Gas 1", width=100)
self.first_gas_labelframe.pack()
self.value_label = Label(self.first_gas_labelframe, text="Value")
self.value_label.pack()
self.unit_label = Label(self.first_gas_labelframe, text="Unit")
self.unit_label.pack()
self.temp_label = Label(self.first_gas_labelframe, text="Temp")
self.temp_label.pack()
self.timer_button = tk.Button(self, text='Start', command=self.start_thread)
self.quit = tk.Button(self, text="QUIT", fg="red", command=root.destroy)
self.quit.pack()
self.gas_list = [self.gas]
def check_thread(self):
if self.thread.is_alive():
root.after(50, self.check_thread)
else:
# Thread completed
self.timer_button.config(state='normal')
def start_thread(self):
self.timer_button.config(state='disabled')
self.thread = threading.Thread(target=self.process)
self.thread.start()
root.after(50, self.check_thread)
def reader(self):
self.read = gas_num.read_registers(0,42)
self.value_label.config(text=self.read[0])
self.unit_label.config(text=self.read[1])
self.temp_label.config(text=self.read[2])
def process(self):
with THREAD_LOCK:
for sen in self.gas_list:
self.proc = Process(target=self.reader, args=(sen,))
self.proc.start()
self.proc.join()
if __name__ == '__main__':
root = tk.Tk()
app = Application()
app.mainloop()
Lock
是确保没有两个线程同时运行。我还禁用了该按钮,以便用户在完成之前无法单击。通过使用root.after
方法,您可以创建在运行前等待一段时间的回调。
就多处理而言,您在单独的进程上运行进程,但一次只能运行一个进程。如果要一次运行多个调用,则需要将join
调用移动到其他位置。我不确定一次运行多少个进程,但您可以执行以下操作:
processes = []
for sen in self.gas_list:
proc = Process(target=self.reader, args=(sen,))
processes.append(proc)
proc.start()
[x.join() for x in processes]
在此实现中,我删除了将proc
赋值为类变量。
我没有库或数据来正确测试所有这些,但它应该可以工作......
编辑
这将启动一个由 6 个进程组成的池,这些进程循环遍历self.gas_list
,将项目传递给self.reader
。当这些完成后,它将检查以确保一秒钟已经过去了(如果没有等待)并重新启动上述过程。这将永远运行,或者直到引发异常。您需要从多处理模块导入Pool
。
def process(self):
with THREAD_LOCK:
pool = Pool(6)
while 1:
start_time = time.time()
pool.map(self.reader, self.gas_list)
execution_time = time.time() - start_time
if execution_time < 1:
time.sleep(1-execution_time)