我正在尝试构建一个支持多处理的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在运行过程中运行?
答案 0 :(得分:1)
最简单的解决方案是将这一切都放在一个单独的线程上。你的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)