Tkinter的应用“冻结”,同时不断对内容(多)

问题描述:

轮询管我有两个脚本:Tkinter的应用“冻结”,同时不断对内容(多)

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日,仍然没有解决方案。

+0

如果您的目标是通过tkinter GUI与命令行应用程序通信,您可能需要查看我的答案[here](https://*.com/questions/21811464/how-can-i-embed -a-蟒-解释器框架中有蟒-使用-Tkinter的/ 46545426#46545426)。它使用一个子进程,用单独的线程来获得输出。 – Oli

+0

由于gui_input和input_done中的递归代码可能会冻结 –

+0

由于gui_input和input_done中的递归代码,它可能会冻结。而不是next_one(a_pipe),之后使用将安排一个独立的进程,因此每个函数调用仍在等待从下一个调用返回/退出无限期,即frame.after(100,next_one,a_pipe ) –

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

+0

这似乎是Unix系统可能的解决方案。不幸的是,我需要一个对Windows来说也很强大的解决方案,因为大多数用户都会在Windows机器上部署它。 Tkinter事件循环文件处理程序说,它们不能在链接文档中的Windows上工作。 – user1318135

+0

在文件处理程序文档中:“此功能在Windows上不可用。” 并在multiprocessing.connection.wait中:“Windows:...请注意,管道句柄和套接字句柄不是可等待的句柄。” – user1318135

+0

这不是在Windows 10中修复的吗?这应该与Linux兼容层一起发布,否则它将无法很好地工作。 –

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

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

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

最近,在桌面应用程序中,我使用Electron来连接到cherrypy服务器,并使用Javascript使用Electron的AJAX请求。最后的图标就是启动服务器和客户端。 这允许我有一个更丰富的部件集,因为网络比Tk更强大。

这将允许你在未来可能有一个web应用程序。

HTH

看来,你正在努力实现的行为,而它的运行与功能进行通信。我认为你的问题可以通过使用生成器来解决。通过生成器,您可以从函数中生成多个值,并将值发送到该函数。

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(...)