Tkinter应用程序冻结'同时轮询Pipe for contents(多处理)

时间:2017-10-17 19:27:16

标签: python python-3.x tkinter python-multiprocessing tkinter-entry

我有两个脚本:

Processor_child.py :其目的是执行大量数据分析和清理操作。当单独运行时(没有Tkinter_parent.py),它必须执行与使用Tkinter_parent.py打包到GUI时相同的操作。

Tkinter_parent.py :其目的是为那些无法直接使用Processor_child的人提供GUI。

在Processor_child中,有for个循环要求用户在每次迭代时输入。这些提示需要出现在Tkinter应用程序中,接受输入并将其发送回Processor_child。

以下代码执行此操作,只要Entry中的数据(由循环添加),就会引发Pipe字段。但是,它似乎经常“冻结”,卡住加载而不是通过代码。有时,它按预期完美运行。 (这些实例中的代码没有变化。)

如何解决此问题/使其更稳定?我在下面评论了“冻结”的位置。正在发生。

Tkinter_parent.py:

### Tkinter_parent.py ###
from tkinter import *
from tkinter.filedialog import askopenfilename
from tkinter import ttk
from multiprocessing import Process, Pipe
import pandas as pd
import Processor_child
import time

class GUI:
    def __init__(self, master):
        self.master = master

def gui_input(message, a_pipe = None):
    def input_done(event=None):
        entry.pack_forget()
        input_label.pack_forget()
        submit_button.pack_forget()
        a_pipe.send(entry.get())
        next_one(a_pipe)

    entry = Entry(frame)
    input_label = ttk.Label(frame, text=message)
    entry.bind("<Return>", input_done)
    submit_button = ttk.Button(frame, text="Submit", command=input_done)
    input_label.pack()
    entry.pack()
    submit_button.pack()

def file_select():
    dataset_path = askopenfilename()

    if __name__ == '__main__':
        pipe1, pipe2 = Pipe()

        some_vars = ['a var', 'another var']
        a_df = pd.read_csv(dataset_path)

        p_review = Process(target=Processor_child.review_with_user, args=(some_vars, a_df, pipe2))
        p_review.start()

        gui_input(pipe1.recv(), pipe1)

        #time.sleep(1)
def next_one(pipe1):
    while pipe1.poll() != True: ### CAUSES CONSTANT LOADING WITHOUT PROGRESSION
        time.sleep(0.1)

    gui_input(pipe1.recv(), pipe1)

if __name__ == '__main__':
    root = Tk()
    my_gui = GUI(root)
    root.style = ttk.Style()
    root.style.configure('my.TButton')
    root.style.configure('my.TLabel')

    canvas = Canvas(root)
    frame = Frame(canvas)
    frame.place()
    canvas.pack(side="left", fill="both", expand=True)
    canvas.create_window((45,50), window=frame, anchor="nw")

    ttk.Button(frame, text="Select", command=file_select).pack()

    root.mainloop()

和processor_child:

### processor_child.py ###
import pandas as pd
from multiprocessing import *
import time

def smart_print(message, a_pipe = None):
    if __name__ == "__main__":
        print(message)
    else:
        a_pipe.send(message)

def review_with_user(var_names, dataset, a_pipe = None):
    affirmed = []
    review_message = 'Yes or no?'

    if __name__ == "__main__":
        review_response = input(review_message)
    else:
        smart_print(review_message, a_pipe)
        while a_pipe.poll() != True:
            time.sleep(0.1)

        review_response = a_pipe.recv()

    if review_response in ['Yes', 'yes']:
        for v in dataset.columns:
            smart_print(dataset[v].dropna(), a_pipe)
            if __name__ == "__main__":
                local_response = input(review_message)
            else:
                while a_pipe.poll() != True:
                    time.sleep(0.1)
                local_response = a_pipe.recv()
            if local_response in ['Yes', 'yes']:
                affirmed.append(v)

        smart_print(affirmed, a_pipe)

if __name__ == "__main__":
    var_names = ['var1', 'var2']
    df = pd.read_csv('dummy.csv')
    review_with_user(var_names, df)

这与更广泛的SO问题How can I implement an input method in a Tkinter parent script, with the displayed prompt and return value being sent back to a child script?有关,并且来自一个已发布但无功能的解决方案。

截至2017年10月23日,仍然没有解决方案。

4 个答案:

答案 0 :(得分:0)

您的Connection .poll()电话正忙着等待并咀嚼CPU。但请注意,Connection对象具有fileno()方法;这意味着您可以使用select / poll调用将进程置于休眠状态,同时等待它们为I / O做好准备。请注意,tkinter事件循环支持file handlers,允许您在不阻止UI的情况下执行此操作。

答案 1 :(得分:0)

最简单的方法是从控制台或gui获取输入,然后将结果发送到子程序。当您从控制台请求输入时,添加一个语句,如果设置了某个变量,则会打开Tkinter,并在那里获取信息。

答案 2 :(得分:0)

考虑以客户端 - 服务器方式编写应用程序。

客户端是Tk应用程序,可以连接到服务器。 服务器,只需执行客户端需要的任何内容。 这样,您可以分离处理。 有几种方法可以做到这一点,比如cherrypy,rabbitmq和类似的。

最近,在台式机应用程序中,我使用Electron连接到一台樱桃服务器,并使用Javascript从Electron请求AJAX。最终的图标只是启动服务器和客户端。 这使得我可以拥有更丰富的小部件集,因为网络比Tk更强大。

这将使您在将来可能拥有一个webapp。

HTH

答案 3 :(得分:0)

您尝试实现的行为似乎是在功能运行时与其进行通信。我认为你的问题可以通过使用发电机来解决。生成器允许您从函数中生成多个值,并将值发送到该函数。

Here是关于生成器的更多信息,如果你想知道它们是如何工作的。

我不完全确定这是否是您希望从程序中获得的行为,但我已修改您的代码以使用生成器而非多处理,并且它不再冻结:

Processor_child.py:

### processor_child.py ###
import pandas as pd
import time


def review_with_user(var_names, dataset):
    affirmed = []
    review_message = 'Yes or no?'

    review_response = yield review_message

    if review_response in ['Yes', 'yes']:
        for v in dataset.columns:
            local_response = yield str(dataset[v].dropna())+"\n"+review_message

        yield affirmed

if __name__ == "__main__":
    var_names = ['var1', 'var2']
    df = pd.read_csv('dummy.csv')
    gen = review_with_user(var_names, df)
    # since it is now a generator, you need yo write some code to communicate with it via the console
    # it doesn't print to the console or recieve input unless you do this manually
    while True:
        try:
            print(next(gen))
        except StopIteration:
            break
        print(gen.send(input()))

Tkinter_parent.py:

### Tkinter_parent.py ###
from tkinter import *
from tkinter.filedialog import askopenfilename
from tkinter import ttk
import pandas as pd
import Processor_child
import time

class GUI:
    def __init__(self, master):
        self.master = master

def gui_input(message, p_review):
    def input_done(event=None):
        entry.pack_forget()
        input_label.pack_forget()
        submit_button.pack_forget()
        try:
            p_review.send(entry.get())
            next_one(p_review)
        except StopIteration:
            # this code is executed when there is no more output from Processor_child.review_with_user
            return

    entry = Entry(frame)
    input_label = ttk.Label(frame, text=message)
    entry.bind("<Return>", input_done)
    submit_button = ttk.Button(frame, text="Submit", command=input_done)
    input_label.pack()
    entry.pack()
    submit_button.pack()

def file_select():
    dataset_path = askopenfilename()

    if __name__ == '__main__':
        some_vars = ['a var', 'another var']
        a_df = pd.read_csv(dataset_path)

        p_review = Processor_child.review_with_user(some_vars, a_df)

        gui_input(next(p_review), p_review)

def next_one(p_review):
    try:
        gui_input(next(p_review), p_review)
    except StopIteration:
        # this code is executed when there is no more output from Processor_child.review_with_user
        return

if __name__ == '__main__':
    root = Tk()
    my_gui = GUI(root)
    root.style = ttk.Style()
    root.style.configure('my.TButton')
    root.style.configure('my.TLabel')

    canvas = Canvas(root)
    frame = Frame(canvas)
    frame.place()
    canvas.pack(side="left", fill="both", expand=True)
    canvas.create_window((45,50), window=frame, anchor="nw")

    ttk.Button(frame, text="Select", command=file_select).pack()

    root.mainloop()

当你在它们上面调用next()并且它们已经完成时,生成器会抛出一个StopIteration异常,所以一定要在适当的时候将next(p_review)p_review.send(...)调用放在try块中。