Tkinter:分离流程

时间:2017-06-19 16:23:22

标签: python tkinter multiprocessing

我正在尝试构建一个支持多处理的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在运行过程中运行?

1 个答案:

答案 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)