Python 多处理,如何 return 可以取消进程的变量

Python multiprocessing, how to return variables for a process that can be cancelled

我从一个 tkinter 应用程序开始,该应用程序带有一个启动特定方法的按钮,从该方法可以在 gui 上使用 matplotlib 绘制数据。

但是我需要能够取消该方法,所以我更改了代码以使用多处理,这按预期工作。问题是绘图不再完成了。

一开始我以为我可以将轴作为参数传递并在目标函数内绘图,但这没有给出任何结果,所以我的下一个想法是返回我想要绘制的点从目标过程中绘制出来并将它们绘制在外面。我认为,这意味着不使用 join(),因为我不希望流程发生任何变化。我尝试使用多处理中的队列和数组但没有成功,可能我在逻辑中遗漏了一些东西。

所以我的问题是,上述 2 种方法之间的正确方法是什么,以及我已有的正确代码扩展。

涉及的代码片段:

进口

from tkinter import *
from tkinter import messagebox
from tkinter import ttk
from tkinter import filedialog

from multiprocessing import Process, Value, Manager, Queue
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

主要内容:

if __name__ == "__main__":
    
    root = Tk()
    ParId(root)
    root.mainloop()

ParId class:

class ParId(Frame):
    def __init__(self, parent, *args, **kwargs):

        Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent

        frame2 = LabelFrame(self.parent, padx = 5, pady = 1)
        frame2.grid(row=0, column=1, padx = 10, pady = 1)

        frame2.rowconfigure(0, weight = 1)
        frame2.rowconfigure(1, weight = 1)
        frame2.columnconfigure(0,weight = 1)
        frame2.columnconfigure(1,weight = 1)
        
        lf = self.create_left_frame(frame2)
        kct_f = self.create_kct_frame(frame2)

        lf.grid(column=0, row=0, sticky = "W")
        kct_f.grid(row=1, column=0)  

    def create_left_frame(self, container):
        frame = ttk.Frame(container)
        start_button = Button(frame, text = "Continue", state = NORMAL, padx = 50, command = self.start_func, pady = 5)
        start_button.grid(row = 3, column=0, padx=0, pady=10, sticky = "W")
        return frame

    def create_kct_frame(self,container):
        frame = ttk.Frame(container)
        self.figure_kct = plt.Figure(figsize=(4,3), dpi=100)
        self.ax_kct = self.figure_kct.add_subplot(111)
        self.ax_kct.grid()
        kct_plot = FigureCanvasTkAgg(self.figure_kct, frame)
        kct_plot.get_tk_widget().grid(pady = 10, padx = 10, row = 0, column = 0 ,sticky = "N")
        return frame

    # function that starts process
    def start_func(self):
        proc = Process(target=myfoo, args=(various args))
        process_window = ProcessWindow(self, proc)
        process_window.launch()

ProcessWindow class:

class ProcessWindow(Toplevel):
    def __init__(self, parent, process):
        Toplevel.__init__(self, parent)
        self.parent = parent
        self.process = process

        labl = Label(self, text = "in progress...")        
        terminate_button = ttk.Button(self, text="Cancel", command=self.cancel)

        labl.grid(row=0,column=0)
        terminate_button.grid(row=1, column=0)

    def cancel(self):
        self.process.terminate()
        self.destroy()
        messagebox.showinfo(title="Cancelled", message='Process was terminated')

    def launch(self):
        self.process.start()
        self.after(10, self.isAlive)               
        
    def isAlive(self):
        if self.process.is_alive():                  
            self.after(100, self.isAlive)           
        elif self:
            # Process finished            
            messagebox.showinfo(message="analysis complete", title="Finished")
            self.destroy()

目标方法:

def myfoo(various arguments):

        all_t = []
        all_v = []

       # rest of the function that populates all_t and all_v, which i want to plot on the main gui

总而言之,我想在 gui(使用 tkinter 和 matplotlib)上绘制一些在外部函数 myfoo 中创建的点,但只有在过程完成后,才不会失去取消它的能力。我正在研究 windows、python 3.7。任何见解都是有帮助的。

我会在这里展示我解决它的方法:

正如 Aaron 指出的那样,我继续使用 Queue,传递给进程和 ProcessWindow class。 myfoo 中的任何内容都被放入队列中,只有当进程完成时,我才从队列中获取超时,请注意我使用了多少次 get()。

对于实际绘图,我刚刚在 ParId 中创建了一个新方法,它更新了轴和图形,并且可以从 ProcesWindow 调用它,因为 ParId 是它的父级。

我不确定这是否是最好的方法,如果我像这样使用队列 memory-wise 是否有任何问题,是否应该特别 closed/cleared 不知何故。

from tkinter import *
from tkinter import messagebox
from tkinter import ttk
from tkinter import filedialog

import time
from multiprocessing import Process, Queue
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


class ParId(Frame):
# function that starts process
    
    def __init__(self, parent, *args, **kwargs):
        Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent

        frame2 = LabelFrame(self.parent, padx = 5, pady = 1)
        frame2.grid(row=0, column=1, padx = 10, pady = 1)

        frame2.rowconfigure(0, weight = 1)
        frame2.rowconfigure(1, weight = 1)
        frame2.columnconfigure(0,weight = 1)
        frame2.columnconfigure(1,weight = 1)
        
        lf = self.create_left_frame(frame2)
        kct_f = self.create_kct_frame(frame2)

        lf.grid(column=0, row=0, sticky = "W")
        kct_f.grid(row=1, column=0)

    def start_func(self):
        self.qu = Queue()
        proc = Process(target=myfoo, args = (self.qu,))
        self.process_window = ProcessWindow(self, proc, self.qu)
        self.process_window.launch()

    def create_left_frame(self, container):
        frame = ttk.Frame(container)
        start_button = Button(frame, text = "Continue", state = NORMAL, padx = 50, command = self.start_func, pady = 5)
        start_button.grid(row = 3, column=0, padx=0, pady=10, sticky = "W")
        return frame

    def create_kct_frame(self,container):
        frame = ttk.Frame(container)
        self.figure_kct = plt.Figure(figsize=(4,3), dpi=100)
        self.ax_kct = self.figure_kct.add_subplot(111)
        self.ax_kct.grid()
        kct_plot = FigureCanvasTkAgg(self.figure_kct, frame)
        kct_plot.get_tk_widget().grid(pady = 10, padx = 10, row = 0, column = 0 ,sticky = "N")
        return frame

    def update_kct_frame(self, all_vel, all_time):
        print("in the update kct")
        self.ax_kct.plot(all_time,all_vel, '*')
        self.figure_kct.canvas.draw()


class ProcessWindow(Toplevel):
    def __init__(self, parent, process, qu):
        Toplevel.__init__(self, parent)
        self.parent = parent
        self.process = process
        self.qu = qu
        
        labl = Label(self, text = "in progress...")        
        terminate_button = ttk.Button(self, text="Cancel", command=self.cancel)
        labl.grid(row=0,column=0)
        terminate_button.grid(row=1, column=0)

    def cancel(self):
        self.process.terminate()
        self.destroy()
        messagebox.showinfo(title="Cancelled", message='Process was terminated')

    def launch(self):
        self.process.start()
        self.after(10, self.isAlive)               
        
    def isAlive(self):
        if self.process.is_alive():                  
            self.after(100, self.isAlive)           
        elif self:
            # Process finished
            self.all_vel = self.qu.get(timeout = 10)
            self.all_time = self.qu.get(timeout = 10)
            print("process finished, v is ", self.all_vel)
            print("process finished, t is", self.all_time)
            self.parent.update_kct_frame(self.all_vel, self.all_time)
            messagebox.showinfo(message="analysis complete", title="Finished")
            self.destroy()


def myfoo(qu):
        all_t = []
        all_v = []

        for i in range(5):
            all_t.append(1000*i)
            all_v.append(10*i)

            time.sleep(1)
            print("i am in the target, appending", all_t[i])

        qu.put(all_v)
        qu.put(all_t)


      
if __name__ == "__main__":
    
    root = Tk()
    ParId(root)
    root.mainloop()